From 9a0e430a84bef11a04b390b5742e26d012ba62dd Mon Sep 17 00:00:00 2001 From: =?utf8?q?Franti=C5=A1ek=20Dvo=C5=99=C3=A1k?= Date: Tue, 29 May 2007 14:19:08 +0000 Subject: [PATCH] Initial import. --- org.glite.lbjp-common.db/.cvsignore | 3 + org.glite.lbjp-common.db/LICENSE | 69 + org.glite.lbjp-common.db/Makefile | 137 + org.glite.lbjp-common.db/examples/db_expire.c | 115 + org.glite.lbjp-common.db/examples/db_test.c | 196 + org.glite.lbjp-common.db/interface/db.h | 344 ++ org.glite.lbjp-common.db/src/db.c | 1039 ++++ org.glite.lbjp-common.server-bones/.cvsignore | 1 + org.glite.lbjp-common.server-bones/Makefile | 95 + .../examples/cnt_example.c | 179 + .../examples/srv_example.c | 224 + .../interface/srvbones.h | 92 + org.glite.lbjp-common.server-bones/src/srvbones.c | 662 +++ org.glite.lbjp-common.trio/.cvsignore | 1 + org.glite.lbjp-common.trio/LICENSE | 69 + org.glite.lbjp-common.trio/Makefile | 114 + org.glite.lbjp-common.trio/interface/escape.h | 59 + org.glite.lbjp-common.trio/interface/trio.h | 187 + org.glite.lbjp-common.trio/src/escape.c | 224 + org.glite.lbjp-common.trio/src/strio.c | 581 ++ org.glite.lbjp-common.trio/src/strio.h | 227 + org.glite.lbjp-common.trio/src/trio.c | 5706 ++++++++++++++++++++ org.glite.lbjp-common.trio/src/triop.h | 138 + org.glite.lbjp-common.trio/test/trio_test.cpp | 85 + 24 files changed, 10547 insertions(+) create mode 100644 org.glite.lbjp-common.db/.cvsignore create mode 100644 org.glite.lbjp-common.db/LICENSE create mode 100644 org.glite.lbjp-common.db/Makefile create mode 100644 org.glite.lbjp-common.db/examples/db_expire.c create mode 100644 org.glite.lbjp-common.db/examples/db_test.c create mode 100644 org.glite.lbjp-common.db/interface/db.h create mode 100644 org.glite.lbjp-common.db/src/db.c create mode 100644 org.glite.lbjp-common.server-bones/.cvsignore create mode 100644 org.glite.lbjp-common.server-bones/Makefile create mode 100644 org.glite.lbjp-common.server-bones/examples/cnt_example.c create mode 100644 org.glite.lbjp-common.server-bones/examples/srv_example.c create mode 100644 org.glite.lbjp-common.server-bones/interface/srvbones.h create mode 100644 org.glite.lbjp-common.server-bones/src/srvbones.c create mode 100644 org.glite.lbjp-common.trio/.cvsignore create mode 100644 org.glite.lbjp-common.trio/LICENSE create mode 100644 org.glite.lbjp-common.trio/Makefile create mode 100644 org.glite.lbjp-common.trio/interface/escape.h create mode 100644 org.glite.lbjp-common.trio/interface/trio.h create mode 100644 org.glite.lbjp-common.trio/src/escape.c create mode 100644 org.glite.lbjp-common.trio/src/strio.c create mode 100644 org.glite.lbjp-common.trio/src/strio.h create mode 100644 org.glite.lbjp-common.trio/src/trio.c create mode 100644 org.glite.lbjp-common.trio/src/triop.h create mode 100644 org.glite.lbjp-common.trio/test/trio_test.cpp diff --git a/org.glite.lbjp-common.db/.cvsignore b/org.glite.lbjp-common.db/.cvsignore new file mode 100644 index 0000000..b7d6c92 --- /dev/null +++ b/org.glite.lbjp-common.db/.cvsignore @@ -0,0 +1,3 @@ +build +doc +reports diff --git a/org.glite.lbjp-common.db/LICENSE b/org.glite.lbjp-common.db/LICENSE new file mode 100644 index 0000000..01b973b --- /dev/null +++ b/org.glite.lbjp-common.db/LICENSE @@ -0,0 +1,69 @@ +LICENSE file for EGEE Middleware +================================ + +Copyright (c) 2004 on behalf of the EU EGEE Project: +The European Organization for Nuclear Research (CERN), +Istituto Nazionale di Fisica Nucleare (INFN), Italy +Datamat Spa, Italy +Centre National de la Recherche Scientifique (CNRS), France +CS Systeme d'Information (CSSI), France +Royal Institute of Technology, Center for Parallel Computers (KTH-PDC), Sweden +Universiteit van Amsterdam (UvA), Netherlands +University of Helsinki (UH.HIP), Finlan +University of Bergen (UiB), Norway +Council for the Central Laboratory of the Research Councils (CCLRC), United Kingdom + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. The end-user documentation included with the redistribution, if +any, must include the following acknowledgment: "This product includes +software developed by The EU EGEE Project (http://cern.ch/eu-egee/)." +Alternatively, this acknowledgment may appear in the software itself, if +and wherever such third-party acknowledgments normally appear. + +4. The names EGEE and the EU EGEE Project must not be +used to endorse or promote products derived from this software without +prior written permission. For written permission, please contact +. + +5. You are under no obligation whatsoever to provide anyone with any +bug fixes, patches, or upgrades to the features, functionality or +performance of the Software ("Enhancements") that you may develop over +time; however, if you choose to provide your Enhancements to The EU +EGEE Project, or if you choose to otherwise publish or distribute your +Enhancements, in source code form without contemporaneously requiring +end users of The EU EGEE Proejct to enter into a separate written license +agreement for such Enhancements, then you hereby grant The EU EGEE Project +a non-exclusive, royalty-free perpetual license to install, use, copy, +modify, prepare derivative works, incorporate into the EGEE Middleware +or any other computer software, distribute, and sublicense your +Enhancements or derivative works thereof, in binary and source code +form (if any), whether developed by The EU EGEE Project or third parties. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL PROJECT OR ITS CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +This software consists of voluntary contributions made by many +individuals on behalf of the EU EGEE Prject. For more information on The +EU EGEE Project, please see http://cern.ch/eu-egee/. For more information on +EGEE Middleware, please see http://egee-jra1.web.cern.ch/egee-jra1/ + + diff --git a/org.glite.lbjp-common.db/Makefile b/org.glite.lbjp-common.db/Makefile new file mode 100644 index 0000000..e54bd9f --- /dev/null +++ b/org.glite.lbjp-common.db/Makefile @@ -0,0 +1,137 @@ +# defaults +top_srcdir=. +builddir=build +stagedir=. +distdir=. +globalprefix=glite +lbutilsprefix=lb-utils +package=glite-lb-utils-db +version=0.2.0 +PREFIX=/opt/glite +flavour=gcc32thr + +glite_location=/opt/glite +mysql_prefix=/opt/mysql +mysql_version=4.1.11 +cppunit_prefix=/opt/cppunit +thrflavour=gcc32pthr +nothrflavour=gcc32 + +-include Makefile.inc +-include ../project/version.properties + +version=${module.version} + +CC=gcc + +VPATH=${top_srcdir}/interface:${top_srcdir}/src:${top_srcdir}/examples + +DEBUG:=-g -O0 -Wall + +CFLAGS:= \ + ${DEBUG} \ + -DVERSION=\"${version}\" \ + -I${stagedir}/include -I${top_srcdir}/src -I. \ + -I${top_srcdir}/interface \ + ${COVERAGE_FLAGS} \ + -I${mysql_prefix}/include -I${mysql_prefix}/include/mysql \ + -D_GNU_SOURCE + +ifdef LBS_DB_PROFILE + CFLAGS:=${CFLAGS} -DLBS_DB_PROFILE +endif + +TEST_LIBS:=-L${cppunit_prefix}/lib -lcppunit +TEST_INC:=-I${cppunit_prefix}/include + +LDFLAGS:=-L${stagedir}/lib ${COVERAGE_FLAGS} + +COMPILE:=libtool --mode=compile ${CC} ${CFLAGS} +LINK:=libtool --mode=link ${CC} -rpath ${stagedir}/lib ${LDFLAGS} +INSTALL:=libtool --mode=install install + +archlib:=lib +host_cpu:=${shell uname -m} +ifeq (${host_cpu},x86_64) + archlib:=lib64 +endif + +ifneq (${mysql_prefix},/usr) + ifeq ($(shell echo ${mysql_version} | cut -d. -f1,2),4.1) + mysqlib := -L${mysql_prefix}/${archlib}/mysql + else + mysqlib := -L${mysql_prefix}/${archlib} + endif +endif + +EXT_LIBS:=${mysqlib} -lmysqlclient -lz -lglite_lbu_trio +OBJS:=db.o +TESTOBJS:=dbtest.o +HDRS:=db.h +LOBJS:=${OBJS:.o=.lo} +LTESTOBJS:=${TESTOBJS:.o=.lo} + +libglite_lbu_db.la: ${LOBJS} + ${LINK} -o $@ $< ${EXT_LIBS} + +libglite_lbu_dbtest.la: ${LTESTOBJS} + ${LINK} -o $@ $< ${EXT_LIBS} + +dbtest.lo dbtest.o: db.c db.h + ${COMPILE} -DGLITE_LBU_DEFAULT_RESULT_BUFFER_LENGTH=10 -c $< -o $@ + +db_test: db_test.lo libglite_lbu_dbtest.la + ${LINK} -o $@ $+ ${EXT_LIBS} + +db_expire: db_expire.lo libglite_lbu_dbtest.la + ${LINK} -o $@ $+ ${EXT_LIBS} + +default all: compile + +compile: libglite_lbu_db.la + +check: + -echo No checks here yet. + +test_coverage: + -mkdir coverage + cd coverage && $(MAKE) -f ../Makefile top_srcdir=../../ COVERAGE_FLAGS="-fprofile-arcs -ftest-coverage" check + cd coverage && for i in `echo ${OBJS} | tr ' ' '\012' | sort -u`; do gcov $$i ; done + +examples: db_test db_expire + +doc: + doxygen C.dox + +stage: compile + $(MAKE) install PREFIX=${stagedir} DOSTAGE=yes + +dist: distsrc distbin + +distsrc: + mkdir -p ${top_srcdir}/${package}-${version} + cd ${top_srcdir} && GLOBIGNORE="${package}-${version}" && cp -Rf * ${package}-${version} + cd ${top_srcdir} && tar -czf ${distdir}/${package}-${version}_src.tar.gz --exclude-from=project/tar_exclude ${package}-${version} + rm -rf ${top_srcdir}/${package}-${version} + +distbin: + $(MAKE) install PREFIX=`pwd`/tmpbuilddir${stagedir} + save_dir=`pwd`; cd tmpbuilddir${stagedir} && tar -czf $$save_dir/${top_srcdir}/${distdir}/${package}-${version}_bin.tar.gz *; cd $$save_dir + rm -rf tmpbuilddir + +install: + -mkdir -p ${PREFIX}/lib + -mkdir -p ${PREFIX}/include/${globalprefix}/${lbutilsprefix} + ${INSTALL} -m 644 ${top_srcdir}/LICENSE ${PREFIX}/share/doc/${package}-${version} + ${INSTALL} -m 755 "libglite_lbu_db.la" "${PREFIX}/lib/libglite_lbu_db.la"; \ + ${INSTALL} -m 644 ${top_srcdir}/interface/${HDRS} ${PREFIX}/include/${globalprefix}/${lbutilsprefix} + +clean: + +%.o %.lo: %.c + ${COMPILE} -c $< + +db.lo: db.c db.h +db_test.lo: libglite_lbu_dbtest.la db.h db_test.c + +.PHONY: default all compile check examples doc stage dist distsrc distbin install clean test_coverage diff --git a/org.glite.lbjp-common.db/examples/db_expire.c b/org.glite.lbjp-common.db/examples/db_expire.c new file mode 100644 index 0000000..012a91b --- /dev/null +++ b/org.glite.lbjp-common.db/examples/db_expire.c @@ -0,0 +1,115 @@ +/* + * Example (and quick test) of prepared statements expirations. + * Use 'SET GLOBAL wait_timeout=...' for experimenting. + * + * Requires existing database with appropriate access and example table: + * + * mysqladmin -u root -p create test + * mysql -u root -p -e 'GRANT ALL on test.* to testuser@localhost' + * ./db_test + * + * Use CS environment variable when using different user/pwd@machine:dbname. + */ + +#include +#include +#include + +#include "db.h" + +#define CS "testuser/@localhost:test" +#define SELECT_CMD "SELECT id, user, info FROM data WHERE user = ?" + +#define dprintf(ARGS) { printf("%s: ", name); printf ARGS; } + + +static void print_blob(unsigned long len, char *blob) { + int i; + for (i = 0; i < len; i++) printf("%02X ", blob[i]); + printf("(='"); + for (i = 0; i < len; i++) printf("%c", blob[i]); + printf("')"); +} + + +static void print_free_result(const char *name, unsigned long *lens, char **res) { + dprintf((" id='%s'=%d\n", res[0], atoi(res[0]))); + + dprintf((" user='%s'\n", res[1])); + + dprintf((" blob=")); + if (res[2] && lens) print_blob(lens[2], res[2]); + else printf("null"); + printf("\n"); + + free(res[0]); + free(res[1]); + free(res[2]); +} + + +int main(int argn, char *argv[]) { + char *name, *user; + const char *cs; + glite_lbu_DBContext ctx; + glite_lbu_Statement stmt; + int caps, i, nr, c; + unsigned long lens[3]; + char *res[3]; + + if ((name = strrchr(argv[0], '/')) != NULL) name++; + else name = argv[0]; + if ((cs = getenv("CS")) == NULL) cs = CS; + + // init + dprintf(("connecting to %s...\n", cs)); + if (glite_lbu_InitDBContext(&ctx) != 0) goto fail; + if (glite_lbu_DBConnect(ctx, cs) != 0) goto failctx; + if ((caps = glite_lbu_DBQueryCaps(ctx)) == -1) goto failcon; + if ((caps & GLITE_LBU_DB_CAP_PREPARED) == 0) { + dprintf(("can't do prepared commands, exiting.")); + goto failcon; + } + // caps + glite_lbu_DBSetCaps(ctx, caps); + dprintf(("capabilities: %d\n", caps)); + + user = NULL; + dprintf(("preparing '%s'...\n", user)); + if ((glite_lbu_PrepareStmt(ctx, SELECT_CMD, &stmt)) != 0) goto failcon; + + do { + user = "cicomexocitl.civ"; + dprintf(("executing '%s'...\n", user)); + if (glite_lbu_ExecStmt(stmt, 1, GLITE_LBU_DB_TYPE_VARCHAR, user) == -1) goto failstmt; + dprintf(("fetching '%s'...\n", user)); + while ((nr = glite_lbu_FetchRow(stmt, 3, lens, res)) > 0) { + dprintf(("Result: n=%d, res=%p\n", nr, res)); + print_free_result(name, lens, res); + } + if (nr < 0) { + dprintf(("fetch '%s' failed\n", user)); + break; + } + dprintf(("\n")); + + c = fgetc(stdin); + } while (c != -1 && (c == '\r' || c == '\n')); + + dprintf(("closing...\n")); + glite_lbu_DBClose(ctx); + glite_lbu_FreeDBContext(ctx); + return 0; + +failstmt: + printf("closing stmt...\n"); + glite_lbu_FreeStmt(&stmt); +failcon: + dprintf(("closing...\n")); + glite_lbu_DBClose(ctx); +failctx: + glite_lbu_FreeDBContext(ctx); +fail: + dprintf(("failed\n")); + return 1; +} diff --git a/org.glite.lbjp-common.db/examples/db_test.c b/org.glite.lbjp-common.db/examples/db_test.c new file mode 100644 index 0000000..02be92a --- /dev/null +++ b/org.glite.lbjp-common.db/examples/db_test.c @@ -0,0 +1,196 @@ +/* + * Example (and quick test) of this DB module. + * + * Requires existing database with appropriate access: + * + * mysqladmin -u root -p create test + * mysql -u root -p -e 'GRANT ALL on test.* to testuser@localhost' + * + * Use CS environment variable when using different user/pwd@machine:dbname. + */ + +#include +#include +#include + +#include "db.h" + +#define CS "testuser/@localhost:test" +#define CREATE_CMD "CREATE TABLE data (\n\ + id INT NOT NULL,\n\ + user VARCHAR(32) NOT NULL,\n\ + info BLOB,\n\ + PRIMARY KEY (id),\n\ + INDEX(user)\n\ +) engine=innodb" +#define DROP_CMD "DROP TABLE data" +#define INSERT_TRIO_CMD "INSERT INTO data (id, user, info) VALUES (%d, %s, %s)" +#define SELECT_TRIO_CMD "SELECT id, user, info FROM data WHERE user = '%s'" +#define INSERT_CMD "INSERT INTO data (id, user, info) VALUES (?, ?, ?)" +#define SELECT_CMD "SELECT id, user, info FROM data WHERE user = ?" + +#define dprintf(ARGS) { printf("%s: ", name); printf ARGS; } + + +static void print_blob(unsigned long len, char *blob) { + int i; + for (i = 0; i < len; i++) printf("%02X ", blob[i]); + printf("(='"); + for (i = 0; i < len; i++) printf("%c", blob[i]); + printf("')"); +} + + +static void print_free_result(const char *name, unsigned long *lens, char **res) { + dprintf((" id='%s'=%d\n", res[0], atoi(res[0]))); + + dprintf((" user='%s'\n", res[1])); + + dprintf((" blob=")); + if (res[2] && lens) print_blob(lens[2], res[2]); + else printf("null"); + printf("\n"); + + free(res[0]); + free(res[1]); + free(res[2]); +} + + +int main(int argn, char *argv[]) { + char *name, *cmd; + const char *cs; + glite_lbu_DBContext ctx; + glite_lbu_Statement stmt; + int caps; + + char blob1[] = "Guess: blob or \000string?"; + char blob2[] = {0, 1, 2, 3, 4, 5}; + + int nr; + char *res[3]; + unsigned long lens[3]; + + if ((name = strrchr(argv[0], '/')) != NULL) name++; + else name = argv[0]; + if ((cs = getenv("CS")) == NULL) cs = CS; + cmd = NULL; + + // init + dprintf(("connecting to %s...\n", cs)); + if (glite_lbu_InitDBContext(&ctx) != 0) goto fail; + if (glite_lbu_DBConnect(ctx, cs) != 0) goto failctx; + if ((caps = glite_lbu_DBQueryCaps(ctx)) == -1) goto failcon; + if ((caps & GLITE_LBU_DB_CAP_PREPARED) == 0) { + dprintf(("can't do prepared commands, exiting.")); + goto failcon; + } + // caps + glite_lbu_DBSetCaps(ctx, caps || GLITE_LBU_DB_CAP_ERRORS); + dprintf(("capabilities: %d\n", caps)); + // create all needed tables and data + dprintf(("creating tables...\n")); + glite_lbu_ExecSQL(ctx, DROP_CMD, NULL); + if (glite_lbu_ExecSQL(ctx, CREATE_CMD, NULL) == -1) goto failcon; + // trio-insert + dprintf(("trio-insert...\n")); + asprintf(&cmd, INSERT_TRIO_CMD, 1, "'hyperochus'", "NULL"); + if (glite_lbu_ExecSQL(ctx, cmd, NULL) != 1) goto failcon; + free(cmd); cmd = NULL; + // prepared-insert + dprintf(("prepare-insert...\n")); + if (glite_lbu_PrepareStmt(ctx, INSERT_CMD, &stmt) != 0) goto failcon; + dprintf(("execute 1. insert...\n")); + if (glite_lbu_ExecStmt(stmt, 3, + GLITE_LBU_DB_TYPE_INT, 2, + GLITE_LBU_DB_TYPE_VARCHAR, "cicomexocitl.civ", + GLITE_LBU_DB_TYPE_BLOB, blob1, sizeof(blob1) - 1) != 1) goto failstmt; + dprintf(("execute 2. insert...\n")); + if (glite_lbu_ExecStmt(stmt, 3, + GLITE_LBU_DB_TYPE_INT, 3, + GLITE_LBU_DB_TYPE_VARCHAR, "tartarus", + GLITE_LBU_DB_TYPE_NULL) != 1) goto failstmt; + dprintf(("execute 3. insert...\n")); + if (glite_lbu_ExecStmt(stmt, 3, + GLITE_LBU_DB_TYPE_INT, 4, + GLITE_LBU_DB_TYPE_VARCHAR, "harpia", + GLITE_LBU_DB_TYPE_BLOB, blob2, sizeof(blob2)) != 1) goto failstmt; + glite_lbu_FreeStmt(&stmt); + dprintf(("\n")); + + // trio-query +{ + const char *user; + + user = "harpia"; + dprintf(("selecting '%s'...\n", user)); + asprintf(&cmd, SELECT_TRIO_CMD, user); + if (glite_lbu_ExecSQL(ctx, cmd, &stmt) == -1) goto failcon; + free(cmd); cmd = NULL; + dprintf(("fetching '%s'...\n", user)); + while ((nr = glite_lbu_FetchRow(stmt, 3, lens, res)) > 0) { + dprintf(("Result: n=%d, res=%p\n", nr, res)); + print_free_result(name, lens, res); + } + if (nr < 0) dprintf(("fetch '%s' failed\n", user)); + dprintf(("closing stmt...\n")); + glite_lbu_FreeStmt(&stmt); + dprintf(("\n")); + + user = "nobody"; + dprintf(("selecting '%s'...\n", user)); + asprintf(&cmd, SELECT_TRIO_CMD, user); + if (glite_lbu_ExecSQL(ctx, cmd, &stmt) == -1) goto failcon; + free(cmd); cmd = NULL; + dprintf(("fetching '%s'...\n", user)); + while ((nr = glite_lbu_FetchRow(stmt, 3, lens, res)) > 0) { + dprintf(("Result: n=%d, res=%p\n", nr, res)); + print_free_result(name, lens, res); + } + if (nr < 0) dprintf(("fetch '%s' failed\n", user)); + dprintf(("closing stmt...\n")); + glite_lbu_FreeStmt(&stmt); + dprintf(("\n")); +} + + // "param" queries +{ + const char *user = NULL; + + dprintf(("preparing '%s'...\n", user)); + if ((glite_lbu_PrepareStmt(ctx, SELECT_CMD, &stmt)) != 0) goto failcon; + + user = "cicomexocitl.civ"; + dprintf(("executing '%s'...\n", user)); + if (glite_lbu_ExecStmt(stmt, 1, GLITE_LBU_DB_TYPE_VARCHAR, user) == -1) goto failstmt; + dprintf(("fetching '%s'...\n", user)); + while ((nr = glite_lbu_FetchRow(stmt, 3, lens, res)) > 0) { + dprintf(("Result: n=%d, res=%p\n", nr, res)); + print_free_result(name, lens, res); + } + if (nr < 0) dprintf(("fetch '%s' failed\n", user)); + dprintf(("\n")); + + dprintf(("closing stmt...\n")); + glite_lbu_FreeStmt(&stmt); + dprintf(("\n")); +} + + dprintf(("closing...\n")); + glite_lbu_DBClose(ctx); + glite_lbu_FreeDBContext(ctx); + return 0; + +failstmt: + printf("closing stmt...\n"); + glite_lbu_FreeStmt(&stmt); +failcon: + dprintf(("closing...\n")); + glite_lbu_DBClose(ctx); +failctx: + glite_lbu_FreeDBContext(ctx); +fail: + free(cmd); + dprintf(("failed\n")); + return 1; +} diff --git a/org.glite.lbjp-common.db/interface/db.h b/org.glite.lbjp-common.db/interface/db.h new file mode 100644 index 0000000..2818a4e --- /dev/null +++ b/org.glite.lbjp-common.db/interface/db.h @@ -0,0 +1,344 @@ +#ifndef GLITE_LBU_DB_H +#define GLITE_LBU_DB_H + +#ident "$Header$" + + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * \file db.h + * \defgroup database Database module + * + * Database modul module API (LB & JP Utils). + * + * There are two ways to access DB here: + * - simple: + * + * SQL commands as single string. All values are incorporated in the SQL command strings. Proper escaping is required. + * - enhanced: + * + * Prepared SQL commands with separated parameters, functions PrepareStmt() and ExecStmt(). All values are delivered in separated buffers. Its faster for multiple using and more secure. + * @{ + */ + + +/** + * Enable transaction support if available. + * + * With disabled transaction can be used transaction functions, they are just ignored. + */ +#define GLITE_LBU_DB_CAP_TRANSACTIONS 1 + +/** + * Check prepared parameters support. + */ +#define GLITE_LBU_DB_CAP_PREPARED 2 + +/** + * Check for getting indexes support. + * + * Needed for QueryIndices call. + */ +#define GLITE_LBU_DB_CAP_INDEX 4 + + +/** + * Print all errors. + * + * Not returned from detection of capabilities. + */ +#define GLITE_LBU_DB_CAP_ERRORS 8 + + +/** + * Database connection context. + */ +typedef struct glite_lbu_DBContext_s *glite_lbu_DBContext; + + +/** + * Prepared statement, used for SQL statement with parameters. + */ +typedef struct glite_lbu_Statement_s *glite_lbu_Statement; + + +/** + * Structure holds date for multi-rows insert. + */ +typedef struct glite_lbu_bufInsert_s *glite_lbu_bufInsert; + + + +/** + * All types of parameteres, they match to the SQL types. + */ +typedef enum { + GLITE_LBU_DB_TYPE_NULL = 0, + GLITE_LBU_DB_TYPE_TINYINT = 1, + GLITE_LBU_DB_TYPE_INT = 2, + GLITE_LBU_DB_TYPE_TINYBLOB = 3, + GLITE_LBU_DB_TYPE_TINYTEXT = 4, + GLITE_LBU_DB_TYPE_BLOB = 5, + GLITE_LBU_DB_TYPE_TEXT = 6, + GLITE_LBU_DB_TYPE_MEDIUMBLOB = 7, + GLITE_LBU_DB_TYPE_MEDIUMTEXT = 8, + GLITE_LBU_DB_TYPE_LONGBLOB = 9, + GLITE_LBU_DB_TYPE_LONGTEXT = 10, + GLITE_LBU_DB_TYPE_VARCHAR = 11, + GLITE_LBU_DB_TYPE_CHAR = 12, + GLITE_LBU_DB_TYPE_DATE = 13, + GLITE_LBU_DB_TYPE_TIME = 14, + GLITE_LBU_DB_TYPE_DATETIME = 15, + GLITE_LBU_DB_TYPE_TIMESTAMP = 16, + GLITE_LBU_DB_TYPE_LAST = 17 +} glite_lbu_DBType; + + + +/** + * Get error state from DB context. + * + * \param[in] ctx context to work with + * \param[out] text error name + * \param[out] desc error description + */ +int glite_lbu_DBError(glite_lbu_DBContext ctx, char **text, char **desc); + + +/** + * Initialize the database context. + * + * \param[out] ctx result context + */ +int glite_lbu_InitDBContext(glite_lbu_DBContext *ctx); + + +/** + * Free database context. + */ +void glite_lbu_FreeDBContext(glite_lbu_DBContext ctx); + + +/** + * Connect to the given database. + * + * \param[out] ctx context to work with + * \param[in] cs connect string user/password\@host:database + * + * \return error code, 0 = OK + */ +int glite_lbu_DBConnect(glite_lbu_DBContext ctx, const char *cs); + + +/** + * Close the connection. + * + * \param[in,out] ctx context to work with + */ +void glite_lbu_DBClose(glite_lbu_DBContext ctx); + + +/** + * Check database version and capabilities. + * + * \param[in,out] ctx context to work with + * + * \return capabilities + * \retval -1 error occured + */ +int glite_lbu_DBQueryCaps(glite_lbu_DBContext ctx); + + +/** + * Set the database capabilities on already initialized context. + * + * It should be find out by DBQueryCaps() first. + * + * \param[in,out] ctx context to work with + * \param[in] caps capabilities to use, should be found out by QueryCaps() + */ +void glite_lbu_DBSetCaps(glite_lbu_DBContext ctx, int caps); + + +/** + * Start transaction. + */ +int glite_lbu_Transaction(glite_lbu_DBContext ctx); + + +/** + * Commit (end) transaction. + */ +int glite_lbu_Commit(glite_lbu_DBContext ctx); + + +/** + * Cancel transaction. + */ +int glite_lbu_Rollback(glite_lbu_DBContext ctx); + + +/** + * \param[in,out] stmt executed SQL statement + * \param[in] n number of items for sure there is enough space in lengths and results + * \param[out] lengths array with lengths (good for data blobs), may be NULL + * \param[out] results array with results, all items are allocated + * + * \retval >0 number of fields of the retrieved row + * \retval 0 no more rows + * \retval -1 error + */ +int glite_lbu_FetchRow(glite_lbu_Statement stmt, unsigned int n, unsigned long *lengths, char **results); + + +/** + * Free the statement structure and destroy its parameters. + * + * Statement will be set to NULL and multiple calls are allowed. + * + * \param[in,out] stmt statement + */ +void glite_lbu_FreeStmt(glite_lbu_Statement *stmt); + + +/** + * Parse and execute one simple SQL statement. + * All values are incorporated int the SQL command string. + * + * \param[in,out] ctx context to work with + * \param[in] cmd SQL command + * \param[out] stmt statement handle with results (makes sense for selects only) + * + * \return number of rows selected, created or affected by update, -1 on error + */ +int glite_lbu_ExecSQL(glite_lbu_DBContext ctx, const char *cmd, glite_lbu_Statement *stmt); + + +/** + * Query for column names of the statement. + * + * It work only for simple API, so only after ExecSQL(). + * + * \param[in,out] stmt the statement handle + * \param[out] cols result array of names + * + * \return error code + */ +int glite_lbu_QueryColumns(glite_lbu_Statement stmt, char **cols); + + +/** + * Retrieve column names of a query simple SQL statement. + * + * \param[in,out] ctx context to work with + * \param[in] table table name + * \param[out] key_names one-dimensional index names array + * \param[out] column_names two-dimensional column names array + * + * \return 0 if OK, nonzero on error + */ +int glite_lbu_QueryIndices(glite_lbu_DBContext ctx, const char *table, char ***key_names, char ****column_names); + + +/** + * Convert time_t into database-specific time string. + * + * The result string can be used directly in SQL commands. + * + * \param[in] t the converted time + * \param[out] str result allocated string + */ +void glite_lbu_TimeToDB(time_t t, char **str); + + +/** + * Convert database-specific time string to time_t. + * + * String is expected in database for (ISO format). + * + * \param[in] str the converted string + * \return result time + */ +time_t glite_lbu_DBToTime(const char *str); + + +/** + * Init data structure for buffered insert + * + * takes table_name and columns string for future multirow insert + * when insert string oversize size_limit or number of rows to be inserted + * overcome record_limit, the real insert is triggered + */ +int glite_lbu_bufferedInsertInit(glite_lbu_DBContext ctx, glite_lbu_bufInsert *bi, void *mysql, const char *table_name, long size_limit, long record_limit, const char * columns); + + +/** + * adds row of n values into n columns into an insert buffer + * if num. of rows or size of data oversteps the limits, real + * multi-row insert is done + */ +int glite_lbu_bufferedInsert(glite_lbu_bufInsert bi, const char *row); + + +/** + * Flush buffered data and free bi structure. + */ +int glite_lbu_bufferedInsertClose(glite_lbu_bufInsert bi); + + +/** + * Prepare the SQL statement. Use glite_lbu_FreeStmt() to free it. + * + * \param[in,out] ctx context to work with + * \param[in] sql SQL command + * \param[out] stmt returned SQL statement + * + * \return error code + */ +int glite_lbu_PrepareStmt(glite_lbu_DBContext ctx, const char *sql, glite_lbu_Statement *stmt); + + +/** + * Execute prepared SQL statement. + * + * \param[in,out] stmt SQL statement + * \param[in] n number of items + * + * Variable parameters (n-times): + * + * always: + * + * \param type DB item type + * + * then one of them: + * + * \param GLITE_LBU_DB_TYPE_TINYINT int c + * \param GLITE_LBU_DB_TYPE_INT long int i + * \param GLITE_LBU_DB_TYPE_...BLOB/TEXT void *b, unsigned long len + * \param GLITE_LBU_DB_TYPE_[VAR]CHAR char *str + * \param GLITE_LBU_DB_TYPE_DATE/TIME/DATETIME time_t t + * \param GLITE_LBU_DB_TYPE_TIMESTAMP time_t t + * \param GLITE_LBU_DB_TYPE_NULL - + * + * \return number of affected rows, -1 on error + */ +int glite_lbu_ExecStmt(glite_lbu_Statement stmt, int n, ...); + + +/** + * @} database group + */ + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/org.glite.lbjp-common.db/src/db.c b/org.glite.lbjp-common.db/src/db.c new file mode 100644 index 0000000..bce48b8 --- /dev/null +++ b/org.glite.lbjp-common.db/src/db.c @@ -0,0 +1,1039 @@ +#ident "$Header$" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "glite/lbu/trio.h" +#include "db.h" + + +#define GLITE_LBU_MYSQL_INDEX_VERSION 40001 +#define GLITE_LBU_MYSQL_PREPARED_VERSION 40102 +#define BUF_INSERT_ROW_ALLOC_BLOCK 1000 +#ifndef GLITE_LBU_DEFAULT_RESULT_BUFFER_LENGTH +#define GLITE_LBU_DEFAULT_RESULT_BUFFER_LENGTH 256 +#endif + + +#define CLR_ERR(CTX) lbu_clrerr((CTX)) +#define ERR(CTX, CODE, DESC) lbu_err((CTX), (CODE), (DESC), __FUNCTION__, __LINE__) +#define STATUS(CTX) ((CTX)->err.code) +#define MY_ERR(CTX) myerr((CTX), __FUNCTION__, __LINE__) +#define MY_ERRSTMT(STMT) myerrstmt((STMT), __FUNCTION__, __LINE__) +#define MY_ISOKSTMT(STMT, RETRY) myisokstmt((STMT), __FUNCTION__, __LINE__, (RETRY)) + +#define USE_TRANS(CTX) ((CTX->caps & GLITE_LBU_DB_CAP_TRANSACTIONS) != 0) + +#define dprintf(CTX, FMT...) if (CTX->caps & GLITE_LBU_DB_CAP_ERRORS) fprintf(stderr, ##FMT) + + +struct glite_lbu_DBContext_s { + MYSQL *mysql; + const char *cs; + int have_caps; + int caps; + struct { + int code; + char *desc; + } err; +}; + + +struct glite_lbu_Statement_s { + glite_lbu_DBContext ctx; + + /* for simple commands */ + MYSQL_RES *result; + + /* for prepared commands */ + MYSQL_STMT *stmt; + unsigned long nrfields; +}; + + +struct glite_lbu_bufInsert_s { + glite_lbu_DBContext ctx; + char *table_name; + char *columns; /* names of columns to be inserted into + * (values separated with commas) */ + char **rows; /* each row hold string of one row to be inserted + * (values separated with commas) */ + long rec_num, /* actual number of rows in structure */ + rec_size; /* approx. size of a real insert string */ + long size_limit, /* size and # of records limit which trigger */ + record_limit; /* real insert; zero means unlimitted */ +}; + + +/* + * mapping glite DB types to mysql types + */ +int glite_type_to_mysql[] = { + MYSQL_TYPE_NULL, + MYSQL_TYPE_TINY, + MYSQL_TYPE_LONG, + MYSQL_TYPE_TINY_BLOB, + MYSQL_TYPE_TINY_BLOB, + MYSQL_TYPE_BLOB, + MYSQL_TYPE_BLOB, + MYSQL_TYPE_MEDIUM_BLOB, + MYSQL_TYPE_MEDIUM_BLOB, + MYSQL_TYPE_LONG_BLOB, + MYSQL_TYPE_LONG_BLOB, + MYSQL_TYPE_VAR_STRING, + MYSQL_TYPE_STRING, + MYSQL_TYPE_DATE, + MYSQL_TYPE_TIME, + MYSQL_TYPE_DATETIME, + MYSQL_TYPE_TIMESTAMP, +}; + + + +static int lbu_clrerr(glite_lbu_DBContext ctx); +static int lbu_err(glite_lbu_DBContext ctx, int code, const char *desc, const char *func, int line); +static int myerr(glite_lbu_DBContext ctx, const char *source, int line); +static int myerrstmt(glite_lbu_Statement stmt, const char *source, int line); +static int myisokstmt(glite_lbu_Statement stmt, const char *source, int line, int *retry); +static int db_connect(glite_lbu_DBContext ctx, const char *cs, MYSQL **mysql); +static void db_close(MYSQL *mysql); +static int transaction_test(glite_lbu_DBContext ctx, MYSQL *m2, int *have_transactions); +static int FetchRowSimple(glite_lbu_DBContext ctx, MYSQL_RES *result, unsigned long *lengths, char **results); +static int FetchRowPrepared(glite_lbu_DBContext ctx, glite_lbu_Statement stmt, unsigned int n, unsigned long *lengths, char **results); +void set_time(MYSQL_TIME *mtime, const time_t time); +time_t get_time(const MYSQL_TIME *mtime); + + +/* ---- common ---- */ + + +int glite_lbu_DBError(glite_lbu_DBContext ctx, char **text, char **desc) { + if (text) *text = strdup(strerror(ctx->err.code)); + if (desc) { + if (ctx->err.desc) *desc = strdup(ctx->err.desc); + else *desc = NULL; + } + + return ctx->err.code; +} + + +int glite_lbu_InitDBContext(glite_lbu_DBContext *ctx) { + *ctx = calloc(1, sizeof **ctx); + return *ctx == NULL ? ENOMEM : 0; +} + + +void glite_lbu_FreeDBContext(glite_lbu_DBContext ctx) { + if (ctx) { + assert(ctx->mysql == NULL); + free(ctx->err.desc); + free(ctx); + } +} + + +int glite_lbu_DBConnect(glite_lbu_DBContext ctx, const char *cs) { + if (db_connect(ctx, cs, &ctx->mysql) != 0) return STATUS(ctx); + return 0; +} + + +void glite_lbu_DBClose(glite_lbu_DBContext ctx) { + db_close(ctx->mysql); + ctx->mysql = NULL; +} + + +int glite_lbu_DBQueryCaps(glite_lbu_DBContext ctx) { + MYSQL *m = ctx->mysql; + MYSQL *m2; + int major,minor,sub,version,caps,have_transactions=0; + const char *ver_s; + + if (ctx->have_caps) return ctx->caps; + + caps = 0; + + ver_s = mysql_get_server_info(m); + if (!ver_s || 3 != sscanf(ver_s,"%d.%d.%d",&major,&minor,&sub)) + return ERR(ctx, EINVAL, "problem retreiving MySQL version"); + version = 10000*major + 100*minor + sub; + + if (version >= GLITE_LBU_MYSQL_INDEX_VERSION) caps |= GLITE_LBU_DB_CAP_INDEX; + if (version >= GLITE_LBU_MYSQL_PREPARED_VERSION) caps |= GLITE_LBU_DB_CAP_PREPARED; + + CLR_ERR(ctx); + + if (db_connect(ctx, ctx->cs, &m2) == 0) { + transaction_test(ctx, m2, &have_transactions); + db_close(m2); + } + if (have_transactions) caps |= GLITE_LBU_DB_CAP_TRANSACTIONS; + + if (STATUS(ctx) == 0) { + ctx->have_caps = 1; + return caps; + } else return -1; +} + + +void glite_lbu_DBSetCaps(glite_lbu_DBContext ctx, int caps) { + ctx->caps = caps; +} + + +int glite_lbu_Transaction(glite_lbu_DBContext ctx) { + if (USE_TRANS(ctx)) { + if (glite_lbu_ExecSQL(ctx, "SET AUTOCOMMIT=0", NULL) < 0) goto err; + if (glite_lbu_ExecSQL(ctx, "BEGIN", NULL) < 0) goto err; + } +err: + return STATUS(ctx); +} + + +int glite_lbu_Commit(glite_lbu_DBContext ctx) { + if (USE_TRANS(ctx)) { + if (glite_lbu_ExecSQL(ctx, "COMMIT", NULL) < 0) goto err; + if (glite_lbu_ExecSQL(ctx, "SET AUTOCOMMIT=1", NULL) < 0) goto err; + } +err: + return STATUS(ctx); +} + + +int glite_lbu_Rollback(glite_lbu_DBContext ctx) { + if (USE_TRANS(ctx)) { + if (glite_lbu_ExecSQL(ctx, "ROLLBACK", NULL) < 0) goto err; + if (glite_lbu_ExecSQL(ctx, "SET AUTOCOMMIT=1", NULL) < 0) goto err; + } +err: + return STATUS(ctx); +} + + +int glite_lbu_FetchRow(glite_lbu_Statement stmt, unsigned int n, unsigned long *lengths, char **results) { + memset(results, 0, n * sizeof(*results)); + if (stmt->result) return FetchRowSimple(stmt->ctx, stmt->result, lengths, results); + else return FetchRowPrepared(stmt->ctx, stmt, n, lengths, results); +} + + +void glite_lbu_FreeStmt(glite_lbu_Statement *stmt) { + if (*stmt) { + if ((*stmt)->result) mysql_free_result((*stmt)->result); + if ((*stmt)->stmt) mysql_stmt_close((*stmt)->stmt); + free(*stmt); + *stmt = NULL; + } +} + + +int glite_lbu_QueryIndices(glite_lbu_DBContext ctx, const char *table, char ***key_names, char ****column_names) { + glite_lbu_Statement stmt = NULL; + + int i,j,ret; + +/* XXX: "show index from" columns. Matches at least MySQL 4.0.11 */ + char *showcol[12]; + int Key_name,Seq_in_index,Column_name,Sub_part; + + char **keys = NULL; + int *cols = NULL; + char **col_names = NULL; + + int nkeys = 0; + + char ***idx = NULL; + + Key_name = Seq_in_index = Column_name = Sub_part = -1; + + if (glite_lbu_ExecSQL(ctx,"show index from states",&stmt)<0) + return STATUS(ctx); + + while ((ret = glite_lbu_FetchRow(stmt,sizeof(showcol)/sizeof(showcol[0]),NULL,showcol)) > 0) { + assert(ret <= sizeof showcol/sizeof showcol[0]); + + if (!col_names) { + col_names = malloc(ret * sizeof col_names[0]); + glite_lbu_QueryColumns(stmt,col_names); + for (i=0; i= 0 && Seq_in_index >= 0 && + Column_name >= 0 && Sub_part >= 0); + + } + + for (i=0; imysql, cmd)) { + /* error occured */ + switch (merr = mysql_errno(ctx->mysql)) { + case 0: + break; + case ER_DUP_ENTRY: + ERR(ctx, EEXIST, mysql_error(ctx->mysql)); + return -1; + break; + case CR_SERVER_LOST: + if (retry_nr <= 0) + do_reconnect = 1; + break; + default: + MY_ERR(ctx); + return -1; + break; + } + } + retry_nr++; + } + + if (stmt) { + *stmt = calloc(1, sizeof(**stmt)); + if (!*stmt) { + ERR(ctx, ENOMEM, NULL); + return -1; + } + (**stmt).ctx = ctx; + (**stmt).result = mysql_store_result(ctx->mysql); + if (!(**stmt).result) { + if (mysql_errno(ctx->mysql)) { + MY_ERR(ctx); + *stmt = NULL; + return -1; + } + } + } else { + MYSQL_RES *r = mysql_store_result(ctx->mysql); + mysql_free_result(r); + } +#ifdef LBS_DB_PROFILE + pid = getpid(); + gettimeofday(&end,NULL); + end.tv_usec -= start.tv_usec; + end.tv_sec -= start.tv_sec; + if (end.tv_usec < 0) { end.tv_sec--; end.tv_usec += 1000000; } + + sum.tv_usec += end.tv_usec; + sum.tv_sec += end.tv_sec + sum.tv_usec / 1000000; + sum.tv_usec -= 1000000 * (sum.tv_usec / 1000000); + fprintf(stderr,"[%d] %s\n[%d] %3ld.%06ld (sum: %3ld.%06ld)\n",pid,txt,pid,end.tv_sec,end.tv_usec,sum.tv_sec,sum.tv_usec); +#endif + + return mysql_affected_rows(ctx->mysql); +} + + +int glite_lbu_QueryColumns(glite_lbu_Statement stmt, char **cols) +{ + int i = 0; + MYSQL_FIELD *f; + + if (!stmt->result) return ERR(stmt->ctx, EINVAL, "QueryColumns implemented only for simple API"); + while ((f = mysql_fetch_field(stmt->result))) cols[i++] = f->name; + return i == 0; +} + + +void glite_lbu_TimeToDB(time_t t, char **str) { + struct tm *tm = gmtime(&t); + + asprintf(str,"'%4d-%02d-%02d %02d:%02d:%02d'",tm->tm_year+1900,tm->tm_mon+1, + tm->tm_mday,tm->tm_hour,tm->tm_min,tm->tm_sec); +} + + +time_t glite_lbu_DBToTime(const char *str) { + struct tm tm; + + memset(&tm,0,sizeof(tm)); + setenv("TZ","UTC",1); tzset(); + sscanf(str,"%4d-%02d-%02d %02d:%02d:%02d", + &tm.tm_year,&tm.tm_mon,&tm.tm_mday, + &tm.tm_hour,&tm.tm_min,&tm.tm_sec); + tm.tm_year -= 1900; + tm.tm_mon--; + + return mktime(&tm); +} + +/* ---- prepared --- */ + +int glite_lbu_PrepareStmt(glite_lbu_DBContext ctx, const char *sql, glite_lbu_Statement *stmt) { + int ret, retry; + MYSQL_RES *meta; + + // init + *stmt = calloc(1, sizeof(**stmt)); + (*stmt)->ctx = ctx; + + // create the SQL command + if (((*stmt)->stmt = mysql_stmt_init(ctx->mysql)) == NULL) + return MY_ERRSTMT(*stmt); + + // prepare the SQL command + retry = 1; + do { + mysql_stmt_prepare((*stmt)->stmt, sql, strlen(sql)); + ret = MY_ISOKSTMT(*stmt, &retry); + } while (ret == 0); + if (ret == -1) goto failed; + + // number of fields (0 for no results) + if ((meta = mysql_stmt_result_metadata((*stmt)->stmt)) != NULL) { + (*stmt)->nrfields = mysql_num_fields(meta); + mysql_free_result(meta); + } else + (*stmt)->nrfields = 0; + + return CLR_ERR(ctx); + +failed: + glite_lbu_FreeStmt(stmt); + return STATUS(ctx); +} + + +int glite_lbu_ExecStmt(glite_lbu_Statement stmt, int n, ...) { + int i; + va_list ap; + glite_lbu_DBType type; + char *pchar; + long int *plint; + MYSQL_TIME *ptime; + glite_lbu_DBContext ctx; + int ret, retry; + MYSQL_BIND *binds = NULL; + void **data = NULL; + unsigned long *lens; + + // gather parameters + if (n) { + binds = calloc(n, sizeof(MYSQL_BIND)); + data = calloc(n, sizeof(void *)); + lens = calloc(n, sizeof(unsigned long *)); + } + va_start(ap, n); + for (i = 0; i < n; i++) { + type = va_arg(ap, glite_lbu_DBType); + switch (type) { + case GLITE_LBU_DB_TYPE_TINYINT: + pchar = binds[i].buffer = data[i] = malloc(sizeof(char)); + *pchar = va_arg(ap, int); + break; + + case GLITE_LBU_DB_TYPE_INT: + plint = binds[i].buffer = data[i] = malloc(sizeof(long int)); + *plint = va_arg(ap, long int); + break; + + case GLITE_LBU_DB_TYPE_TINYBLOB: + case GLITE_LBU_DB_TYPE_TINYTEXT: + case GLITE_LBU_DB_TYPE_BLOB: + case GLITE_LBU_DB_TYPE_TEXT: + case GLITE_LBU_DB_TYPE_MEDIUMBLOB: + case GLITE_LBU_DB_TYPE_MEDIUMTEXT: + case GLITE_LBU_DB_TYPE_LONGBLOB: + case GLITE_LBU_DB_TYPE_LONGTEXT: + binds[i].buffer = va_arg(ap, void *); + binds[i].length = &lens[i]; + lens[i] = va_arg(ap, unsigned long); + break; + + case GLITE_LBU_DB_TYPE_VARCHAR: + case GLITE_LBU_DB_TYPE_CHAR: + binds[i].buffer = va_arg(ap, char *); + binds[i].length = &lens[i]; + lens[i] = binds[i].buffer ? strlen((char *)binds[i].buffer) : 0; + break; + + case GLITE_LBU_DB_TYPE_DATE: + case GLITE_LBU_DB_TYPE_TIME: + case GLITE_LBU_DB_TYPE_DATETIME: + case GLITE_LBU_DB_TYPE_TIMESTAMP: + ptime = binds[i].buffer = data[i] = malloc(sizeof(MYSQL_TIME)); + set_time(ptime, va_arg(ap, time_t)); + break; + + case GLITE_LBU_DB_TYPE_NULL: + break; + + default: + assert("unimplemented parameter assign" == NULL); + break; + } + binds[i].buffer_type = glite_type_to_mysql[type]; + } + va_end(ap); + + // bind parameters + if (mysql_stmt_bind_param(stmt->stmt, binds) != 0) { + MY_ERRSTMT(stmt); + goto failed; + } + + // run + ctx = stmt->ctx; + retry = 1; + do { + mysql_stmt_execute(stmt->stmt); + ret = MY_ISOKSTMT(stmt, &retry); + } while (ret == 0); + if (ret == -1) goto failed; + + // result + retry = 1; + do { + mysql_stmt_store_result(stmt->stmt); + ret = MY_ISOKSTMT(stmt, &retry); + } while (ret == 0); + if (ret == -1) goto failed; + + // free params + for (i = 0; i < n; i++) free(data[i]); + free(data); + free(binds); + free(lens); + CLR_ERR(ctx); + return mysql_stmt_affected_rows(stmt->stmt); + +failed: + for (i = 0; i < n; i++) free(data[i]); + free(data); + free(binds); + free(lens); + return -1; +} + + +int glite_lbu_bufferedInsertInit(glite_lbu_DBContext ctx, glite_lbu_bufInsert *bi, void *mysql, const char *table_name, long size_limit, long record_limit, const char *columns) +{ + *bi = calloc(1, sizeof(*bi)); + (*bi)->ctx = ctx; + (*bi)->table_name = strdup(table_name); + (*bi)->columns = strdup(columns); + (*bi)->rec_num = 0; + (*bi)->rec_size = 0; + (*bi)->rows = calloc(record_limit, sizeof(*((*bi)->rows)) ); + (*bi)->size_limit = size_limit; + (*bi)->record_limit = record_limit; + + return CLR_ERR(ctx); +} + + +static int flush_bufferd_insert(glite_lbu_bufInsert bi) +{ + char *stmt, *vals, *temp; + long i; + + + if (!bi->rec_num) + return STATUS(bi->ctx); + + asprintf(&vals,"(%s)", bi->rows[0]); + for (i=1; i < bi->rec_num; i++) { + // XXX: use string add (preallocated memory) + asprintf(&temp,"%s,(%s)", vals, bi->rows[i]); + free(vals); vals = temp; temp = NULL; + free(bi->rows[i]); + bi->rows[i] = NULL; + } + + trio_asprintf(&stmt, "insert into %|Ss(%|Ss) values %s;", + bi->table_name, bi->columns, vals); + + if (glite_lbu_ExecSQL(bi->ctx,stmt,NULL) < 0) { + if (STATUS(bi->ctx) == EEXIST) + CLR_ERR(bi->ctx); + } + + /* reset bi counters */ + bi->rec_size = 0; + bi->rec_num = 0; + + free(vals); + free(stmt); + + return STATUS(bi->ctx); +} + + +int glite_lbu_bufferedInsert(glite_lbu_bufInsert bi, const char *row) +{ + bi->rows[bi->rec_num++] = strdup(row); + bi->rec_size += strlen(row); + + if ((bi->size_limit && bi->rec_size >= bi->size_limit) || + (bi->record_limit && bi->rec_num >= bi->record_limit)) + { + if (flush_bufferd_insert(bi)) + return STATUS(bi->ctx); + } + + return CLR_ERR(bi->ctx); +} + + +static void free_buffered_insert(glite_lbu_bufInsert bi) { + long i; + + free(bi->table_name); + free(bi->columns); + for (i=0; i < bi->rec_num; i++) { + free(bi->rows[i]); + } + free(bi->rows); +} + + +int glite_lbu_bufferedInsertClose(glite_lbu_bufInsert bi) +{ + if (flush_bufferd_insert(bi)) + return STATUS(bi->ctx); + free_buffered_insert(bi); + + return CLR_ERR(bi->ctx); +} + + +/* + * helping compatibility function: clear error from the context + */ +static int lbu_clrerr(glite_lbu_DBContext ctx) { + ctx->err.code = 0; + if (ctx->err.desc) { + free(ctx->err.desc); + ctx->err.desc = NULL; + } + return 0; +} + + +/* + * helping compatibility function: sets error on the context + */ +static int lbu_err(glite_lbu_DBContext ctx, int code, const char *desc, const char *func, int line) { + if (code) { + ctx->err.code = code; + free(ctx->err.desc); + ctx->err.desc = desc ? strdup(desc) : NULL; + dprintf(ctx, "[db %d] %s:%d %s\n", getpid(), func, line, desc); + return code; + } else + return ctx->err.code; +} + + +/* + * helping function: find oud mysql error and sets on the context + */ +static int myerr(glite_lbu_DBContext ctx, const char *source, int line) { + return lbu_err(ctx, EIO, mysql_error(ctx->mysql), source, line); +} + + +/* + * helping function: find oud mysql stmt error and sets on the context + */ +static int myerrstmt(glite_lbu_Statement stmt, const char *source, int line) { + return lbu_err(stmt->ctx, EIO, mysql_stmt_error(stmt->stmt), source, line); +} + + +/* + * Ehelping function: error handle + * + * \return -1 failed + * \return 0 retry + * \return 1 OK + */ +static int myisokstmt(glite_lbu_Statement stmt, const char *source, int line, int *retry) { + switch (mysql_stmt_errno(stmt->stmt)) { + case 0: + return 1; + break; + case ER_DUP_ENTRY: + lbu_err(stmt->ctx, EEXIST, mysql_stmt_error(stmt->stmt), source, line); + return -1; + break; + case CR_SERVER_LOST: + if (*retry > 0) { + (*retry)--; + return 0; + } else + return -1; + break; + default: + myerrstmt(stmt, source, line); + return -1; + break; + } +} + + +/* + * mysql connect + */ +static int db_connect(glite_lbu_DBContext ctx, const char *cs, MYSQL **mysql) { + char *buf = NULL; + char *host,*user,*pw,*db; + char *slash,*at,*colon; + int ret; + + // needed for SQL result parameters + assert(sizeof(int) >= sizeof(my_bool)); + + if (!cs) return ERR(ctx, EINVAL, "connect string not specified"); + + if (!(*mysql = mysql_init(NULL))) return ERR(ctx, ENOMEM, NULL); + + mysql_options(*mysql, MYSQL_READ_DEFAULT_FILE, "my"); + + host = user = pw = db = NULL; + + buf = strdup(cs); + slash = strchr(buf,'/'); + at = strrchr(buf,'@'); + colon = strrchr(buf,':'); + + if (!slash || !at || !colon) { + free(buf); + db_close(*mysql); + *mysql = NULL; + return ERR(ctx, EINVAL, "Invalid DB connect string"); + } + + *slash = *at = *colon = 0; + host = at+1; + user = buf; + pw = slash+1; + db = colon+1; + + /* ljocha: CLIENT_FOUND_ROWS added to make authorization check + * working in update_notif(). + * Hope it does not break anything else */ + if (!mysql_real_connect(*mysql,host,user,pw,db,0,NULL,CLIENT_FOUND_ROWS)) { + free(buf); + ret = MY_ERR(ctx); + glite_lbu_DBClose(ctx); + return ret; + } + free(buf); + + ctx->cs = cs; + return 0; +} + + +/* + * mysql close + */ +static void db_close(MYSQL *mysql) { + if (mysql) mysql_close(mysql); +} + + +/* + * test transactions capability: + * + * 1) with connection 1 create testing table test + * 2) with connection 1 insert a value + * 3) with connection 2 look for a value, transactions are for no error and + * no items found + * 4) with connection 1 commit and drop the table + */ +static int transaction_test(glite_lbu_DBContext ctx, MYSQL *m2, int *have_transactions) { + MYSQL *m1; + char *desc, *cmd_create, *cmd_insert, *cmd_select, *cmd_drop; + int retval; + int err; + pid_t pid; + + ctx->caps |= GLITE_LBU_DB_CAP_TRANSACTIONS; + pid = getpid(); + *have_transactions = 0; + + asprintf(&cmd_create, "CREATE TABLE test%d (item INT) ENGINE='innodb'", pid); + asprintf(&cmd_insert, "INSERT INTO test%d (item) VALUES (1)", pid); + asprintf(&cmd_select, "SELECT item FROM test%d", pid); + asprintf(&cmd_drop, "DROP TABLE test%d", pid); + + m1 = ctx->mysql; + //glite_lbu_ExecSQL(ctx, cmd_drop, NULL); + if (glite_lbu_ExecSQL(ctx, cmd_create, NULL) != 0) goto err1; + if (glite_lbu_Transaction(ctx) != 0) goto err2; + if (glite_lbu_ExecSQL(ctx, cmd_insert, NULL) != 1) goto err2; + + ctx->mysql = m2; + if ((retval = glite_lbu_ExecSQL(ctx, cmd_select, NULL)) == -1) goto err2; + + ctx->mysql = m1; + if (glite_lbu_Commit(ctx) != 0) goto err2; + if (glite_lbu_ExecSQL(ctx, cmd_drop, NULL) != 0) goto err1; + +#ifdef LBS_DB_PROFILE + fprintf(stderr, "[%d] use_transactions = %d\n", getpid(), USE_TRANS(ctx)); +#endif + + *have_transactions = retval == 0; + goto ok; +err2: + err = ctx->err.code; + desc = ctx->err.desc; + glite_lbu_ExecSQL(ctx, cmd_drop, NULL); + ctx->err.code = err; + ctx->err.desc = desc; +err1: +ok: + free(cmd_create); + free(cmd_insert); + free(cmd_select); + free(cmd_drop); + return STATUS(ctx); +} + + +/* + * simple version of the fetch + */ +static int FetchRowSimple(glite_lbu_DBContext ctx, MYSQL_RES *result, unsigned long *lengths, char **results) { + MYSQL_ROW row; + int nr, i; + unsigned long *len; + + CLR_ERR(ctx); + + if (!(row = mysql_fetch_row(result))) { + if (mysql_errno((MYSQL *) ctx->mysql)) { + MY_ERR(ctx); + return -1; + } else return 0; + } + + nr = mysql_num_fields(result); + len = mysql_fetch_lengths(result); + for (i=0; inrfields) { + ERR(ctx, EINVAL, "bad number of result fields"); + return -1; + } + + // space for results + if (n) binds = calloc(n, sizeof(MYSQL_BIND)); + if (!lengths) { + lens = calloc(n, sizeof(unsigned long)); + lengths = lens; + } + for (i = 0; i < n; i++) { + binds[i].buffer_type = MYSQL_TYPE_VAR_STRING; + binds[i].buffer_length = GLITE_LBU_DEFAULT_RESULT_BUFFER_LENGTH - 1; + binds[i].length = &lengths[i]; + binds[i].buffer = results[i] = calloc(1, GLITE_LBU_DEFAULT_RESULT_BUFFER_LENGTH); + } + if (mysql_stmt_bind_result(stmt->stmt, binds) != 0) goto failedstmt; + + // fetch data, all can be truncated + retry = 1; + do { + switch(mysql_stmt_fetch(stmt->stmt)) { +#ifdef MYSQL_DATA_TRUNCATED + case MYSQL_DATA_TRUNCATED: +#endif + case 0: + ret = 1; break; + case 1: ret = MY_ISOKSTMT(stmt, &retry); break; + case MYSQL_NO_DATA: ret = 0; goto quit; /* it's OK */ + default: ERR(ctx, EIO, "other fetch error"); goto failed; + } + } while (ret == 0); + if (ret == -1) goto failed; + + // check if all fileds had enough buffer space + for (i = 0; i < n; i++) { + // fetch the rest if needed + if (lengths[i] > binds[i].buffer_length) { + unsigned int fetched; + + fetched = binds[i].buffer_length; + if ((results[i] = realloc(results[i], lengths[i] + 1)) == NULL) { + ERR(ctx, ENOMEM, "insufficient memory for field data"); + goto failed; + } + results[i][lengths[i]] = '\000'; + binds[i].buffer = results[i] + fetched; + binds[i].buffer_length = lengths[i] - fetched; + + retry = 1; + do { + switch (mysql_stmt_fetch_column(stmt->stmt, binds + i, i, fetched)) { + case 0: ret = 1; break; + case 1: ret = MY_ISOKSTMT(stmt, &retry); break; + case MYSQL_NO_DATA: ret = 0; goto quit; /* it's OK */ + default: ERR(ctx, EIO, "other fetch error"); goto failed; + } + } while (ret == 0); + if (ret == -1) goto failed; + } + } + + CLR_ERR(ctx); + free(binds); + free(lens); + return n; + +failedstmt: + MY_ERRSTMT(stmt); +failed: + ret = -1; +quit: + free(binds); + free(lens); + for (i = 0; i < n; i++) { + free(results[i]); + results[i] = NULL; + } + return ret; +} + + +void set_time(MYSQL_TIME *mtime, const time_t time) { + struct tm tm; + + gmtime_r(&time, &tm); + memset(mtime, 0, sizeof *mtime); + mtime->year = tm.tm_year + 1900; + mtime->month = tm.tm_mon + 1; + mtime->day = tm.tm_mday; + mtime->hour = tm.tm_hour; + mtime->minute = tm.tm_min; + mtime->second = tm.tm_sec; +} + + +time_t get_time(const MYSQL_TIME *mtime) { + struct tm tm; + + memset(&tm, 0, sizeof(tm)); + setenv("TZ","UTC",1); tzset(); + tm.tm_year = mtime->year - 1900; + tm.tm_mon = mtime->month - 1; + tm.tm_mday = mtime->day; + tm.tm_hour = mtime->hour; + tm.tm_min = mtime->minute; + tm.tm_sec = mtime->second; + + return mktime(&tm); +} diff --git a/org.glite.lbjp-common.server-bones/.cvsignore b/org.glite.lbjp-common.server-bones/.cvsignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/org.glite.lbjp-common.server-bones/.cvsignore @@ -0,0 +1 @@ +build diff --git a/org.glite.lbjp-common.server-bones/Makefile b/org.glite.lbjp-common.server-bones/Makefile new file mode 100644 index 0000000..100ef16 --- /dev/null +++ b/org.glite.lbjp-common.server-bones/Makefile @@ -0,0 +1,95 @@ +# defaults +top_srcdir=. +stagedir=. +distdir=. +globalprefix=glite +lbprefix=lb +package=glite-lb-server-bones +version=0.0.1 +PREFIX=/opt/glite + +glite_location=/opt/glite + +CC=gcc + +-include Makefile.inc +-include ../project/version.properties + +version=${module.version} + +VPATH=${top_srcdir}/src:${top_srcdir}/examples + +DEBUG:=-g -O0 -Wall +CFLAGS:= ${DEBUG} -I${top_srcdir}/interface +LDFLAGS:= + +ifdef LB_PROF + CFLAGS:= ${CFLAGS} -pg -g + LDFLAGS:= ${LDFLAGS} -pg +endif + +COMPILE:=libtool --mode=compile ${CC} ${CFLAGS} +LINK:=libtool --mode=link ${CC} -rpath ${stagedir}/lib ${LDFLAGS} +INSTALL:=libtool --mode=install install + +STATICLIB:=libglite_lb_server_bones.a +LTLIB:=libglite_lb_server_bones.la + +OBJS:=srvbones.o +LOBJS:=${OBJS:.o=.lo} + +HDRS:=srvbones.h + +default all: compile + +compile: ${STATICLIB} ${LTLIB} example + +${STATICLIB}: ${OBJS} + ar crv $@ ${OBJS} + ranlib $@ + +${LTLIB}: ${LOBJS} + ${LINK} -o $@ ${LOBJS} + +stage: compile + $(MAKE) install PREFIX=${stagedir} DOSTAGE=yes + +check: + -echo "No unit tests so far." + +example: srv_example cnt_example + +srv_example: srv_example.o + ${LINK} -o $@ ${LTLIB} srv_example.o + +cnt_example: cnt_example.o + ${LINK} -o $@ cnt_example.o + +doc: + +dist: distsrc distbin + +distsrc: + mkdir -p ${top_srcdir}/${package}-${version} + cd ${top_srcdir} && GLOBIGNORE="${package}-${version}" && cp -Rf * ${package}-${version} + cd ${top_srcdir} && tar -czf ${distdir}/${package}-${version}_src.tar.gz --exclude-from=project/tar_exclude ${package}-${version} + rm -rf ${top_srcdir}/${package}-${version} + +distbin: + $(MAKE) install PREFIX=`pwd`/tmpbuilddir${stagedir} + save_dir=`pwd`; cd tmpbuilddir${stagedir} && tar -czf $$save_dir/${top_srcdir}/${distdir}/${package}-${version}_bin.tar.gz *; cd $$save_dir + rm -rf tmpbuilddir + +install: + mkdir -p ${PREFIX}/include/${globalprefix}/${lbprefix} + mkdir -p ${PREFIX}/lib + ${INSTALL} -m 644 ${LTLIB} ${PREFIX}/lib + if [ x${DOSTAGE} = xyes ]; then \ + ${INSTALL} -m 644 ${STATICLIB} ${PREFIX}/lib ; \ + cd ${top_srcdir}/interface && install -m 644 ${HDRS} ${PREFIX}/include/${globalprefix}/${lbprefix} ; \ + fi + +clean: + +%.o: %.c + ${COMPILE} -c $< diff --git a/org.glite.lbjp-common.server-bones/examples/cnt_example.c b/org.glite.lbjp-common.server-bones/examples/cnt_example.c new file mode 100644 index 0000000..80b6af3 --- /dev/null +++ b/org.glite.lbjp-common.server-bones/examples/cnt_example.c @@ -0,0 +1,179 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef dprintf +#define dprintf(x) { if (debug) printf x; fflush(stdout); } +#endif + +#define DEF_MSG "Test message\n" +#define DEF_PORT 9999 + +static struct option opts[] = { + { "help", no_argument, NULL, 'h'}, + { "debug", no_argument, NULL, 'd'}, + { "msg", required_argument, NULL, 'm'}, + { "port", required_argument, NULL, 'p'}, + { "repeat", required_argument, NULL, 'r'}, +}; + +int debug = 0; +int port = DEF_PORT; +char *msg = NULL; + +static int writen(int fd, char *ptr, int nbytes); +static int readln(int fd, char *out); + +static void usage(char *me) +{ + fprintf(stderr, + "usage: %s [option]\n" + " -h, --help print this screen\n" + " -d, --debug prints debug messages\n" + " -m, --msg message to send\n" + " -p, --port service port\n", me); +} + + +int main(int argc, char **argv) +{ + struct sockaddr_in addr; + char buff[512], + *me; + int opt, + sock, + n; + int repeat = 1; + + me = strrchr(argv[0], '/'); + if ( me ) me++; else me = argv[0]; + while ( (opt = getopt_long(argc, argv,"p:m:hdr:", opts, NULL)) != EOF ) + { + switch ( opt ) + { + case 'm': + msg = strdup(optarg); + break; + case 'p': + port = atoi(optarg); + break; + case 'd': debug = 1; break; + case 'r': repeat = atoi(optarg); break; + case 'h': usage(me); return 0; + case '?': usage(me); return 1; + } + } + + bzero((char *) &addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + addr.sin_port = htons(port); + if ( (sock = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) + { + perror("socket"); + exit(1); + } + if ( connect(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0 ) + { + perror("connect"); + exit(1); + } + n = strlen(msg? msg: DEF_MSG); + for (;repeat; repeat--) { + if ( writen(sock, msg? msg: DEF_MSG, n) != n ) + { + dprintf(("error writing message\n")); + exit(1); + } + printf("reply: "); fflush(stdout); + n = readln(sock, buff); + if ( n < 0 ) + { + perror("read() reply error"); + return 1; + } + writen(0, buff, n); + } + close(sock); + + return 0; +} + +int writen(int fd, char *ptr, int nbytes) +{ + int nleft, nwritten; + + nleft = nbytes; + dprintf(("start writing %d bytes\n", nbytes)); + while ( nleft > 0 ) + { + nwritten = write(fd, ptr, nleft); + dprintf(("written %d bytes", nwritten)); + if ( nwritten <= 0 ) + return (nwritten); + + nleft -= nwritten; + ptr += nwritten; + dprintf((" (left %d bytes)\n", nleft)); + } + + dprintf(("written %d bytes (return: %d)\n", nwritten, nbytes - nleft)); + return (nbytes - nleft); +} + +#define BUFFER_SZ 512 + +int readln(int fd, char *out) +{ + static char buffer[BUFFER_SZ]; + static char *buffer_end = buffer; + int n; + + + dprintf(("reading line\n")); + while ( 1 ) { + if ( buffer_end - buffer ) { + /* buffer contains data + */ + char *endl; + + dprintf(("nonempty buffer\n")); + if ( (endl = memchr(buffer, '\n', buffer_end-buffer)) ) { + int linesz = endl-buffer+1; + + dprintf(("using buffer data\n")); + memcpy(out, buffer, linesz); + if ( endl+1 != buffer_end ) + memmove(buffer, endl+1, buffer_end-endl-1); + buffer_end -= linesz; + return linesz; + } + } + dprintf(("reading...\n")); + n = read(fd, buffer_end, BUFFER_SZ-(buffer_end-buffer)); + if ( n < 0 ) { + if ( errno == EAGAIN ) continue; + dprintf(("reading error\n")); + return n; + } + else if ( n == 0 ) { + int ret = buffer_end-buffer; + dprintf(("end of reading - returning %d bytes\n", ret)); + memcpy(out, buffer, ret); + buffer_end = buffer; + return ret; + } + + dprintf(("read %d bytes\n", n)); + buffer_end += n; + } + + return 0; +} diff --git a/org.glite.lbjp-common.server-bones/examples/srv_example.c b/org.glite.lbjp-common.server-bones/examples/srv_example.c new file mode 100644 index 0000000..0a19ab4 --- /dev/null +++ b/org.glite.lbjp-common.server-bones/examples/srv_example.c @@ -0,0 +1,224 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "srvbones.h" + +#ifndef dprintf +#define dprintf(x) { if (debug) printf x; fflush(stdout); } +#endif + +#define sizofa(a) (sizeof(a)/sizeof((a)[0])) + +int debug = 1; + +static int writen(int fd, char *ptr, int nbytes); +static int readln(int fd, char *out, int nbytes); + +static int new_conn(int, struct timeval *, void *); +static int reject(int); +static int disconnect(int, struct timeval *, void *); + +static int echo(int, struct timeval *, void *); +static int upper_echo(int, struct timeval *, void *); + +#define ECHO_PORT 9999 +#define UPPER_ECHO_PORT 9998 + +#define SRV_ECHO 0 +#define SRV_UPPER_ECHO 1 + +static struct glite_srvbones_service service_table[] = { + { "Echo Service", -1, new_conn, echo, reject, disconnect }, + { "Upper Echo Service", -1, new_conn, upper_echo, reject, disconnect } +}; + +int main(void) +{ + struct sockaddr_in myaddr; + + + if ( ((service_table[SRV_ECHO].conn = socket(AF_INET, SOCK_STREAM, 0)) == -1) + || ((service_table[SRV_UPPER_ECHO].conn = socket(AF_INET, SOCK_STREAM, 0)) == -1) ) + { + perror("socket"); + exit(1); + } + + bzero((char *) &myaddr, sizeof(myaddr)); + myaddr.sin_family = AF_INET; + myaddr.sin_addr.s_addr = htonl(INADDR_ANY); + myaddr.sin_port = htons(ECHO_PORT); + if ( bind(service_table[SRV_ECHO].conn, (struct sockaddr *)&myaddr, sizeof(myaddr)) == -1 ) + { + perror("bind"); + exit(1); + } + bzero((char *) &myaddr, sizeof(myaddr)); + myaddr.sin_family = AF_INET; + myaddr.sin_addr.s_addr = htonl(INADDR_ANY); + myaddr.sin_port = htons(UPPER_ECHO_PORT); + if ( bind(service_table[SRV_UPPER_ECHO].conn, (struct sockaddr *)&myaddr, sizeof(myaddr)) == -1 ) + { + perror("bind"); + exit(1); + } + + if ( listen(service_table[SRV_ECHO].conn, 10) + || listen(service_table[SRV_UPPER_ECHO].conn, 10) ) + { + perror("listen()"); + exit(1); + } + + + glite_srvbones_set_param(GLITE_SBPARAM_SLAVES_COUNT, 1); + glite_srvbones_run(NULL, service_table, sizofa(service_table), 1); + + + return 0; +} + +int upper_echo(int fd, struct timeval *to, void *data) +{ + int n, i; + char line[80]; + + n = readln(fd, line, 80); + if ( n < 0 ) + { + perror("read() message"); + return n; + } + else if ( n == 0 ) + return ENOTCONN; + + for ( i = 0; i < n; i++ ) + line[i] = toupper(line[i]); + + if ( writen(fd, line, n) != n ) + { + perror("write() message back"); + return -1; + } + + return 0; +} + +int echo(int fd, struct timeval *to, void *data) +{ + int n; + char line[80]; + + n = readln(fd, line, 80); + dprintf(("%d bytes read\n", n)); + if ( n < 0 ) + { + perror("read() message"); + return n; + } + else if ( n == 0 ) + return ENOTCONN; + + if ( writen(fd, line, n) != n ) + { + perror("write() message back"); + return -1; + } + + return 0; +} + +int new_conn(int conn, struct timeval *to, void *cdata) +{ + dprintf(("srv-bones example: new_conn handler\n")); + return 0; +} + +int reject(int conn) +{ + dprintf(("srv-bones example: reject handler\n")); + return 0; +} + +int disconnect(int conn, struct timeval *to, void *cdata) +{ + dprintf(("srv-bones example: disconnect handler\n")); + return 0; +} + +int writen(int fd, char *ptr, int nbytes) +{ + int nleft, nwritten; + + nleft = nbytes; + dprintf(("start writing %d bytes\n", nbytes)); + while ( nleft > 0 ) { + nwritten = write(fd, ptr, nleft); + dprintf(("written %d bytes", nwritten)); + if ( nwritten <= 0 ) + return (nwritten); + + nleft -= nwritten; + ptr += nwritten; + dprintf((" (left %d bytes)\n", nleft)); + } + + dprintf(("written %d bytes (return: %d)\n", nwritten, nbytes - nleft)); + return (nbytes - nleft); +} + +#define BUFFER_SZ 512 + +int readln(int fd, char *out, int nbytes) +{ + static char buffer[BUFFER_SZ]; + static char *buffer_end = buffer; + int n; + + + dprintf(("reading line\n")); + while ( 1 ) { + if ( buffer_end - buffer ) { + /* buffer contains data + */ + char *endl; + + dprintf(("nonempty buffer\n")); + if ( (endl = memchr(buffer, '\n', buffer_end-buffer)) ) { + int linesz = endl-buffer+1; + + memcpy(out, buffer, linesz); + if ( endl+1 != buffer_end ) memmove(buffer, endl+1, buffer_end-endl-1); + buffer_end -= linesz; + return linesz; + } + } + + dprintf(("reding...\n")); + n = read(fd, buffer_end, BUFFER_SZ-(buffer_end-buffer)); + if ( n < 0 ) { + if ( errno == EAGAIN ) n = 0; + else return n; + } + if ( n == 0 ) { + int ret = buffer_end-buffer; + dprintf(("end of reading - returning %d bytes\n", ret)); + memcpy(out, buffer, ret); + buffer_end = buffer; + return ret; + } + dprintf(("read %d bytes\n", n)); + + buffer_end += n; + } + + return 0; +} diff --git a/org.glite.lbjp-common.server-bones/interface/srvbones.h b/org.glite.lbjp-common.server-bones/interface/srvbones.h new file mode 100644 index 0000000..f238705 --- /dev/null +++ b/org.glite.lbjp-common.server-bones/interface/srvbones.h @@ -0,0 +1,92 @@ +#ifndef __ORG_GLITE_LB_SERVER_BONES_BONES_H__ +#define __ORG_GLITE_LB_SERVER_BONES_BONES_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum _glite_srvbones_param_t { + GLITE_SBPARAM_SLAVES_COUNT, /**< number of slaves */ + GLITE_SBPARAM_SLAVE_OVERLOAD, /**< queue items per slave */ + GLITE_SBPARAM_SLAVE_CONNS_MAX, /**< commit suicide after that many connections */ + +/* NULL for timeouts means infinity */ + GLITE_SBPARAM_IDLE_TIMEOUT, /**< keep idle connection that long (timeval) */ + GLITE_SBPARAM_CONNECT_TIMEOUT, /**< timeout for establishing a connection (timeval) */ + GLITE_SBPARAM_REQUEST_TIMEOUT, /**< timeout for a single request (timeval)*/ +} glite_srvbones_param_t; + +typedef int (*slave_data_init_hnd)(void **); + +struct glite_srvbones_service { + char *id; /**< name of the service */ + int conn; /**< listening socket */ + +/** Handler called by slave on a newly established connection, + * i.e. after accept(2). + * \param[in] conn the accepted connection + * \param[inout] timeout don't consume more, update with the remaining time + * \param[inout] user_data arbitrary user data passed among the functions + */ + int (*on_new_conn_hnd)( + int conn, + struct timeval *timeout, + void *user_data + ); + + +/** Handler called by slave to serve each request. + * \param[in] conn connection to work with + * \param[inout] timeout don't consume more, update with the remaining time + * \param[inout] user_data arbitrary user data passed among the functions + * + * \retval 0 OK, connection remains open + * \retval ENOTCON terminated gracefully, bones will clean up + * \retval >0 other POSIX errno, non-fatal error + * \retval <0 fatal error, terminate slave + */ + int (*on_request_hnd)( + int conn, + struct timeval *timeout, + void *user_data + ); + +/** Handler called by master to reject connection on server overload. + * Should kick off the client quickly, not imposing aditional load + * on server or blocking long time. + */ + int (*on_reject_hnd)(int conn); + +/** Handler called by slave before closing the connection. + * Perform server-side cleanup, and terminate the connection gracefully + * if there is a way to do so (the disconnect is server-initiated). + * close(conn) is called by bones then. + * \param[in] conn connection to work with + * \param[inout] timeout don't consume more time + * \param[inout] user_data arbitrary user data passed among the functions + */ + int (*on_disconnect_hnd)( + int conn, + struct timeval *timeout, + void *user_data + ); +}; + +extern int glite_srvbones_set_param(glite_srvbones_param_t param, ...); + + +/** Main server function. + * + * \param[in] slave_data_init_hnd callback initializing user data on every slave + */ +extern int glite_srvbones_run( + slave_data_init_hnd slave_data_init, + struct glite_srvbones_service *service_table, + size_t table_sz, + int dbg); + +#ifdef __cplusplus +} +#endif + +#endif /* __ORG_GLITE_LB_SERVER_BONES_BONES_H__ */ diff --git a/org.glite.lbjp-common.server-bones/src/srvbones.c b/org.glite.lbjp-common.server-bones/src/srvbones.c new file mode 100644 index 0000000..bced95c --- /dev/null +++ b/org.glite.lbjp-common.server-bones/src/srvbones.c @@ -0,0 +1,662 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "srvbones.h" + +/* defaults for GLITE_SBPARAM_* */ + +#define SLAVES_COUNT 5 /* default number of slaves */ +#define SLAVE_OVERLOAD 10 /* queue items per slave */ +#define SLAVE_REQS_MAX 500 /* commit suicide after that many connections */ +#define IDLE_TIMEOUT 30 /* keep idle connection that many seconds */ +#define CONNECT_TIMEOUT 5 /* timeout for establishing a connection */ +#define REQUEST_TIMEOUT 10 /* timeout for a single request */ +#define NEW_CLIENT_DURATION 10 /* how long a client is considered new, i.e. busy + connection is not closed to serve other clients */ + +#ifndef dprintf +#define dprintf(x) { if (debug) printf x; } +#endif + +#ifdef LB_PROF +extern void _start (void), etext (void); +#endif + +static int running = 0; +static int debug = 0; +static volatile int die = 0, + child_died = 0; +static unsigned long clnt_dispatched = 0, + clnt_accepted = 0; + +static struct glite_srvbones_service *services; +static int services_ct; + +static int set_slaves_ct = SLAVES_COUNT; +static int set_slave_overload = SLAVE_OVERLOAD; +static int set_slave_reqs_max = SLAVE_REQS_MAX; +static struct timeval set_idle_to = {IDLE_TIMEOUT, 0}; +static struct timeval set_connect_to = {CONNECT_TIMEOUT, 0}; +static struct timeval set_request_to = {REQUEST_TIMEOUT, 0}; + +static int dispatchit(int, int, int); +static int do_sendmsg(int, int, unsigned long, int); +static int do_recvmsg(int, int *, unsigned long *, int *); +static int check_timeout(struct timeval, struct timeval, struct timeval); +static void catchsig(int); +static void catch_chld(int sig); +static int slave(int (*)(void **), int); + +static void glite_srvbones_set_slaves_ct(int); +static void glite_srvbones_set_slave_overload(int); +static void glite_srvbones_set_slave_conns_max(int); +static void set_timeout(struct timeval *,struct timeval *); + +int glite_srvbones_set_param(glite_srvbones_param_t param, ...) +{ + va_list ap; + + if ( running ) { + dprintf(("Attempting to set srv-bones parameter on running server")); + return -1; + } + + va_start(ap, param); + switch ( param ) { + case GLITE_SBPARAM_SLAVES_COUNT: + glite_srvbones_set_slaves_ct(va_arg(ap,int)); break; + case GLITE_SBPARAM_SLAVE_OVERLOAD: + glite_srvbones_set_slave_overload(va_arg(ap,int)); break; + case GLITE_SBPARAM_SLAVE_CONNS_MAX: + glite_srvbones_set_slave_conns_max(va_arg(ap,int)); break; + case GLITE_SBPARAM_IDLE_TIMEOUT: + set_timeout(&set_idle_to,va_arg(ap,struct timeval *)); break; + case GLITE_SBPARAM_CONNECT_TIMEOUT: + set_timeout(&set_connect_to,va_arg(ap,struct timeval *)); break; + case GLITE_SBPARAM_REQUEST_TIMEOUT: + set_timeout(&set_request_to,va_arg(ap,struct timeval *)); break; + } + va_end(ap); + + return 0; +} + +int glite_srvbones_run( + slave_data_init_hnd slave_data_init, + struct glite_srvbones_service *service_table, + size_t table_sz, + int dbg) +{ + struct sigaction sa; + sigset_t sset; + int sock_slave[2], i; + + + assert(service_table); + assert(table_sz > 0); + + services = service_table; + services_ct = table_sz; + debug = dbg; + + setlinebuf(stdout); + setlinebuf(stderr); + dprintf(("Master pid %d\n", getpid())); + + if ( socketpair(AF_UNIX, SOCK_STREAM, 0, sock_slave) ) + { + perror("socketpair()"); + return 1; + } + + memset(&sa, 0, sizeof(sa)); assert(sa.sa_handler == NULL); + sa.sa_handler = catchsig; + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + sa.sa_handler = catch_chld; + sigaction(SIGCHLD, &sa, NULL); + + sa.sa_handler = SIG_IGN; + sigaction(SIGUSR1, &sa, NULL); + + sigemptyset(&sset); + sigaddset(&sset, SIGCHLD); + sigaddset(&sset, SIGTERM); + sigaddset(&sset, SIGINT); + sigprocmask(SIG_BLOCK, &sset, NULL); + + for ( i = 0; i < set_slaves_ct; i++ ) + slave(slave_data_init, sock_slave[1]); + + while ( !die ) + { + fd_set fds; + int ret, mx; + + + FD_ZERO(&fds); + FD_SET(sock_slave[0], &fds); + for ( i = 0, mx = sock_slave[0]; i < services_ct; i++ ) + { + FD_SET(services[i].conn, &fds); + if ( mx < services[i].conn ) mx = services[i].conn; + } + + sigprocmask(SIG_UNBLOCK, &sset, NULL); + ret = select(mx+1, &fds, NULL, NULL, NULL); + sigprocmask(SIG_BLOCK, &sset, NULL); + + if ( ret == -1 && errno != EINTR ) + { + if ( debug ) perror("select()"); + else syslog(LOG_CRIT,"select(): %m"); + + return 1; + } + + if ( child_died ) + { + int pid; + + while ( (pid = waitpid(-1, NULL, WNOHANG)) > 0 ) + { + if ( !die ) + { + int newpid = slave(slave_data_init, sock_slave[1]); + dprintf(("[master] Servus mortuus [%d] miraculo resurrexit [%d]\n", pid, newpid)); + } + } + child_died = 0; + continue; + } + + if ( die ) continue; + + + if (FD_ISSET(sock_slave[0],&fds)) { + /* slave accepted a request + */ + unsigned long a; + + if ( (recv(sock_slave[0], &a, sizeof(a), MSG_WAITALL) == sizeof(a)) + && (a <= clnt_dispatched) + && (a > clnt_accepted || clnt_accepted > clnt_dispatched) ) + clnt_accepted = a; + } + + for ( i = 0; i < services_ct; i++ ) + if ( FD_ISSET(services[i].conn, &fds) + && dispatchit(sock_slave[0], services[i].conn ,i) ) + /* Be carefull!!! + * This must break this for cykle but start the + * while (!die) master cykle from the top also + */ + break; + } + + dprintf(("[master] Terminating on signal %d\n", die)); + if (!debug) syslog(LOG_INFO, "Terminating on signal %d\n", die); + kill(0, die); + + return 0; +} + +static int dispatchit(int sock_slave, int sock, int sidx) +{ + struct sockaddr_in a; + unsigned char *pom; + int conn, + alen, ret; + + + alen = sizeof(a); + if ( (conn = accept(sock, (struct sockaddr *)&a, &alen)) < 0 ) + { + if (debug) + { + perror("accept()"); + return 1; + } + else + { + syslog(LOG_ERR, "accept(): %m"); + sleep(5); + return -1; + } + } + + getpeername(conn, (struct sockaddr *)&a, &alen); + pom = (char *) &a.sin_addr.s_addr; + dprintf(("[master] %s connection from %d.%d.%d.%d:%d\n", + services[sidx].id? services[sidx].id: "", + (int)pom[0], (int)pom[1], (int)pom[2], (int)pom[3], + ntohs(a.sin_port))); + + ret = 0; + if ( ( clnt_dispatched < clnt_accepted /* wraparound */ + || clnt_dispatched - clnt_accepted < set_slaves_ct * set_slave_overload) + && !(ret = do_sendmsg(sock_slave, conn, clnt_dispatched++, sidx)) ) + { + /* all done + */ + dprintf(("[master] Dispatched %lu, last known served %lu\n", + clnt_dispatched-1, clnt_accepted)); + } + else + { + services[sidx].on_reject_hnd(conn); + dprintf(("[master] Rejected new connection due to overload\n")); + if ( !debug ) syslog(LOG_ERR, "Rejected new connection due to overload\n"); + } + + close(conn); + if (ret) + { + perror("sendmsg()"); + if ( !debug ) syslog(LOG_ERR, "sendmsg(): %m"); + } + + + return 0; +} + + +static int slave(slave_data_init_hnd data_init_hnd, int sock) +{ + sigset_t sset; + struct sigaction sa; + struct timeval client_done, + client_start, + new_client_duration = { NEW_CLIENT_DURATION, 0 }; + + void *clnt_data = NULL; + int conn = -1, + srv = -1, + req_cnt = 0, + sockflags, + h_errno, + pid, i, + first_request = 0; /* 1 -> first request from connected client expected */ + + + + if ( (pid = fork()) ) return pid; + +#ifdef LB_PROF + monstartup((u_long)&_start, (u_long)&etext); +#endif + + srandom(getpid()+time(NULL)); + + for ( i = 0; i < services_ct; i++ ) + close(services[i].conn); + + sigemptyset(&sset); + sigaddset(&sset, SIGTERM); + sigaddset(&sset, SIGINT); + sigaddset(&sset, SIGUSR1); + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = catchsig; + sigaction(SIGUSR1, &sa, NULL); + + if ( (sockflags = fcntl(sock, F_GETFL, 0)) < 0 + || fcntl(sock, F_SETFL, sockflags | O_NONBLOCK) < 0 ) + { + dprintf(("[%d] fcntl(master_sock): %s\n", getpid(), strerror(errno))); + if ( !debug ) syslog(LOG_CRIT, "fcntl(master_sock): %m"); + exit(1); + } + + if ( data_init_hnd && data_init_hnd(&clnt_data) ) + /* + * XXX: what if the error remains and master will start new slave + * again and again? + * + * Then we are in a deep shit. + */ + exit(1); + + while ( !die && (req_cnt < set_slave_reqs_max || (conn >= 0 && first_request))) + { + fd_set fds; + int max = sock, + connflags, + newconn = -1, + newsrv = -1; + + enum { KICK_DONT = 0, KICK_IDLE, KICK_LOAD, KICK_HANDLER, KICK_COUNT } + kick_client = KICK_DONT; + + static char * kicks[] = { + "don't kick", + "idle client", + "high load", + "no request handler", + "request count limit reached", + }; + unsigned long seq; + struct timeval now,to; + + + FD_ZERO(&fds); + FD_SET(sock, &fds); + if ( conn >= 0 ) FD_SET(conn, &fds); + if ( conn > sock ) max = conn; + + to = set_idle_to; + sigprocmask(SIG_UNBLOCK, &sset, NULL); + switch (select(max+1, &fds, NULL, NULL, to.tv_sec >= 0 ? &to : NULL)) + { + case -1: + if ( errno != EINTR ) + { + dprintf(("[%d] select(): %s\n", getpid(), strerror(errno))); + if ( !debug ) syslog(LOG_CRIT, "select(): %m"); + exit(1); + } + continue; + + case 0: + if ( conn < 0 ) continue; + + default: + break; + } + sigprocmask(SIG_BLOCK, &sset, NULL); + + gettimeofday(&now,NULL); + + if ( conn >= 0 && FD_ISSET(conn, &fds) ) + { + /* + * serve the request + */ + int rv; + + dprintf(("[%d] incoming request\n", getpid())); + + if ( !services[srv].on_request_hnd ) + { + kick_client = KICK_HANDLER; + } else { + req_cnt++; + first_request = 0; + to = set_request_to; + if ((rv = services[srv].on_request_hnd(conn,to.tv_sec>=0 ? &to : NULL,clnt_data)) == ENOTCONN) { + if (services[srv].on_disconnect_hnd + && (rv = services[srv].on_disconnect_hnd(conn,NULL,clnt_data))) + { + dprintf(("[%d] disconnect handler: %s, terminating\n",getpid(),strerror(rv))); + exit(1); + } + close(conn); + conn = -1; + srv = -1; + dprintf(("[%d] Connection closed\n", getpid())); + } + else if (rv > 0) { + /* non-fatal error -> close connection and contiue + * XXX: likely to leak resources but can we call on_disconnect_hnd() on error? + */ + close(conn); + conn = -1; + srv = -1; + dprintf(("[%d] %s, connection closed\n",getpid(),strerror(rv))); + continue; + } + else if ( rv < 0 ) { + /* unknown error -> clasified as FATAL -> kill slave + */ + dprintf(("[%d] %s, terminating\n",getpid(),strerror(-rv))); + exit(1); + } + else { + dprintf(("[%d] request done\n", getpid())); + gettimeofday(&client_done, NULL); + } + + if (!check_timeout(new_client_duration,client_start,now)) continue; + + } + } else { + if (conn >= 0 && check_timeout(set_idle_to, client_done, now)) + kick_client = KICK_IDLE; + } + + if ( (conn < 0 || !first_request) && FD_ISSET(sock, &fds) && req_cnt < set_slave_reqs_max ) + { + /* Prefer slaves with no connection, then kick idle clients, + * active ones last. Wait less if we have serviced a request in the meantime. + * Tuned for HZ=100 timer. */ + if ( conn >= 0 ) usleep( kick_client || FD_ISSET(conn, &fds) ? 11000 : 21000); + if ( do_recvmsg(sock, &newconn, &seq, &newsrv) ) switch ( errno ) + { + case EINTR: /* XXX: signals are blocked */ + case EAGAIN: + continue; + default: dprintf(("[%d] recvmsg(): %s\n", getpid(), strerror(errno))); + if (!debug) syslog(LOG_CRIT,"recvmsg(): %m\n"); + exit(1); + } + kick_client = KICK_LOAD; + } + + if (req_cnt >= set_slave_reqs_max && !first_request) kick_client = KICK_COUNT; + + if ( kick_client && conn >= 0 ) + { + if ( services[srv].on_disconnect_hnd ) + services[srv].on_disconnect_hnd(conn, NULL, clnt_data); + close(conn); + conn = -1; + srv = -1; + dprintf(("[%d] Connection closed, %s\n", getpid(), kicks[kick_client])); + } + + if ( newconn >= 0 ) + { + int ret; + + conn = newconn; + srv = newsrv; + gettimeofday(&client_start, NULL); + + switch ( send(sock, &seq, sizeof(seq), 0) ) + { + case -1: + if (debug) perror("send()"); + else syslog(LOG_CRIT, "send(): %m\n"); + exit(1); + + case sizeof(seq): + break; + + default: dprintf(("[%d] send(): incomplete message\n", getpid())); + exit(1); + } + + req_cnt++; + dprintf(("[%d] serving %s connection %lu\n", getpid(), + services[srv].id? services[srv].id: "", seq)); + + connflags = fcntl(conn, F_GETFL, 0); + if ( fcntl(conn, F_SETFL, connflags | O_NONBLOCK) < 0 ) + { + dprintf(("[%d] can't set O_NONBLOCK mode (%s), closing.\n", getpid(), strerror(errno))); + if ( !debug ) syslog(LOG_ERR, "can't set O_NONBLOCK mode (%s), closing.\n", strerror(errno)); + close(conn); + conn = srv = -1; + continue; + } + + to = set_connect_to; + if ( services[srv].on_new_conn_hnd + && (ret = services[srv].on_new_conn_hnd(conn, to.tv_sec >= 0 ? &to : NULL, clnt_data)) ) + { + dprintf(("[%d] Connection not estabilished, err = %d.\n", getpid(),ret)); + if ( !debug ) syslog(LOG_ERR, "Connection not estabilished, err = %d.\n",ret); + close(conn); + conn = srv = -1; + if (ret < 0) exit(1); + continue; + } + gettimeofday(&client_done, NULL); + first_request = 1; + } + } + + if ( die ) + { + dprintf(("[%d] Terminating on signal %d\n", getpid(), die)); + if ( !debug ) syslog(LOG_INFO, "Terminating on signal %d", die); + } + + if (conn >= 0 && services[srv].on_disconnect_hnd ) + services[srv].on_disconnect_hnd(conn, NULL, clnt_data); + + dprintf(("[%d] Terminating after %d requests\n", getpid(), req_cnt)); + if ( !debug ) syslog(LOG_INFO, "Terminating after %d requests", req_cnt); + + + exit(0); +} + +static void catchsig(int sig) +{ + die = sig; +} + +static void catch_chld(int sig) +{ + child_died = 1; +} + +static int check_timeout(struct timeval timeout, struct timeval before, struct timeval after) +{ + return (timeout.tv_usec <= after.tv_usec - before.tv_usec) ? + (timeout.tv_sec <= after.tv_sec - before.tv_sec) : + (timeout.tv_sec < after.tv_sec - before.tv_sec); +} + +#define MSG_BUFSIZ 30 + +/* + * send socket sock through socket to_sock + */ +static int do_sendmsg(int to_sock, int sock, unsigned long clnt_dispatched, int srv) +{ + struct msghdr msg = {0}; + struct cmsghdr *cmsg; + struct iovec sendiov; + int myfds, /* file descriptors to pass. */ + *fdptr; + char buf[CMSG_SPACE(sizeof myfds)]; /* ancillary data buffer */ + char sendbuf[MSG_BUFSIZ]; /* to store unsigned int + \0 */ + + + memset(sendbuf, 0, sizeof(sendbuf)); + snprintf(sendbuf, sizeof(sendbuf), "%u %lu", srv, clnt_dispatched); + + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &sendiov; + msg.msg_iovlen = 1; + sendiov.iov_base = sendbuf; + sendiov.iov_len = sizeof(sendbuf); + + msg.msg_control = buf; + msg.msg_controllen = sizeof buf; + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + fdptr = (int *)CMSG_DATA(cmsg); + *fdptr = sock; + + msg.msg_controllen = cmsg->cmsg_len; + /* send fd to server-slave to do rest of communication */ + if (sendmsg(to_sock, &msg, 0) < 0) + return 1; + + return 0; +} + + +/* receive socket sock through socket from_sock */ +static int do_recvmsg(int from_sock, int *sock, unsigned long *clnt_accepted,int *srv) +{ + struct msghdr msg = {0}; + struct cmsghdr *cmsg; + struct iovec recviov; + int myfds; /* file descriptors to pass. */ + char buf[CMSG_SPACE(sizeof(myfds))]; /* ancillary data buffer */ + char recvbuf[MSG_BUFSIZ]; + + + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &recviov; + msg.msg_iovlen = 1; + recviov.iov_base = recvbuf; + recviov.iov_len = sizeof(recvbuf); + + msg.msg_control = buf; + msg.msg_controllen = sizeof buf; + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + msg.msg_controllen = cmsg->cmsg_len; + + if (recvmsg(from_sock, &msg, 0) < 0) + return 1; + + *sock = *((int *)CMSG_DATA(cmsg)); + sscanf(recvbuf, "%u %lu", srv, clnt_accepted); + + return 0; +} + +static void glite_srvbones_set_slaves_ct(int n) +{ + set_slaves_ct = (n == -1)? SLAVES_COUNT: n; +} + +static void glite_srvbones_set_slave_overload(int n) +{ + set_slave_overload = (n == -1)? SLAVE_OVERLOAD: n; +} + +static void glite_srvbones_set_slave_conns_max(int n) +{ + set_slave_reqs_max = (n == -1)? SLAVE_REQS_MAX: n; +} + +static void set_timeout(struct timeval *to, struct timeval *val) +{ + if (val) { + /* XXX: why not, negative timeouts don't make any sense, IMHO */ + assert(val->tv_sec >= 0); + *to = *val; + } + else to->tv_sec = -1; +} diff --git a/org.glite.lbjp-common.trio/.cvsignore b/org.glite.lbjp-common.trio/.cvsignore new file mode 100644 index 0000000..c795b05 --- /dev/null +++ b/org.glite.lbjp-common.trio/.cvsignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/org.glite.lbjp-common.trio/LICENSE b/org.glite.lbjp-common.trio/LICENSE new file mode 100644 index 0000000..01b973b --- /dev/null +++ b/org.glite.lbjp-common.trio/LICENSE @@ -0,0 +1,69 @@ +LICENSE file for EGEE Middleware +================================ + +Copyright (c) 2004 on behalf of the EU EGEE Project: +The European Organization for Nuclear Research (CERN), +Istituto Nazionale di Fisica Nucleare (INFN), Italy +Datamat Spa, Italy +Centre National de la Recherche Scientifique (CNRS), France +CS Systeme d'Information (CSSI), France +Royal Institute of Technology, Center for Parallel Computers (KTH-PDC), Sweden +Universiteit van Amsterdam (UvA), Netherlands +University of Helsinki (UH.HIP), Finlan +University of Bergen (UiB), Norway +Council for the Central Laboratory of the Research Councils (CCLRC), United Kingdom + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. The end-user documentation included with the redistribution, if +any, must include the following acknowledgment: "This product includes +software developed by The EU EGEE Project (http://cern.ch/eu-egee/)." +Alternatively, this acknowledgment may appear in the software itself, if +and wherever such third-party acknowledgments normally appear. + +4. The names EGEE and the EU EGEE Project must not be +used to endorse or promote products derived from this software without +prior written permission. For written permission, please contact +. + +5. You are under no obligation whatsoever to provide anyone with any +bug fixes, patches, or upgrades to the features, functionality or +performance of the Software ("Enhancements") that you may develop over +time; however, if you choose to provide your Enhancements to The EU +EGEE Project, or if you choose to otherwise publish or distribute your +Enhancements, in source code form without contemporaneously requiring +end users of The EU EGEE Proejct to enter into a separate written license +agreement for such Enhancements, then you hereby grant The EU EGEE Project +a non-exclusive, royalty-free perpetual license to install, use, copy, +modify, prepare derivative works, incorporate into the EGEE Middleware +or any other computer software, distribute, and sublicense your +Enhancements or derivative works thereof, in binary and source code +form (if any), whether developed by The EU EGEE Project or third parties. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL PROJECT OR ITS CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +This software consists of voluntary contributions made by many +individuals on behalf of the EU EGEE Prject. For more information on The +EU EGEE Project, please see http://cern.ch/eu-egee/. For more information on +EGEE Middleware, please see http://egee-jra1.web.cern.ch/egee-jra1/ + + diff --git a/org.glite.lbjp-common.trio/Makefile b/org.glite.lbjp-common.trio/Makefile new file mode 100644 index 0000000..96a8386 --- /dev/null +++ b/org.glite.lbjp-common.trio/Makefile @@ -0,0 +1,114 @@ +# Default values +top_srcdir=. +builddir=build +top_builddir=${top_srcdir}/${builddir} +stagedir=. +distdir=. +globalprefix=glite +package=glite-lb-utils-trio +version=0.0.0 +PREFIX=/opt/glite +lbuprefix=lbu + +-include Makefile.inc +-include ../project/version.properties + +version=${module.version} + +VPATH=${top_srcdir}/src:${top_srcdir}/test::${top_srcdir}/doc + +CC=gcc + +DEBUG:=-g -O0 -Wall + +CFLAGS:=${DEBUG} -I${top_srcdir}/src -I${top_srcdir}/interface -I. -DDATAGRID_EXTENSION + +COMPILE:=libtool --mode=compile ${CC} +LINK:=libtool --mode=link ${CC} -rpath ${stagedir}/lib ${LDFLAGS} +LINKXX:=libtool --mode=link ${CXX} -rpath ${stagedir}/lib ${LDFLAGS} +INSTALL:=libtool --mode=install install + +REPORTS:=${top_srcdir}/reports +TEST_LIBS:=-L${cppunit_prefix}/lib -lcppunit -ldl +TEST_INC:=-I${cppunit_prefix}/include + + +OBJS:=trio.o strio.o escape.o +LOBJS:=${OBJS:.o=.lo} + +HDRS:=trio.h escape.h + +STATICLIB:=libglite_lbu_trio.a +LTLIB:=libglite_lbu_trio.la + +default: all + +all compile: ${STATICLIB} ${LTLIB} + +# to use libtool versioning correcty, we should have: +# +# current = major + minor + offset +# revision = patch +# age = minor +# +# where offset is a sum of maximal released minor's of all previous major's +# +# version_info=-version-info `echo ${version} | cut -d. -f1,2 | tr . :` + +# counted minors: n/a +offset=0 + +version_info:=-version-info ${shell \ + perl -e '$$,=":"; @F=split "\\.","${version}"; print $$F[0]+$$F[1]+${offset},$$F[2],$$F[1]' } + +${STATICLIB}: ${OBJS} + ar crv $@ ${OBJS} + ranlib $@ + +${LTLIB}: ${OBJS} + ${LINK} ${version_info} -o $@ ${LOBJS} + +stage: compile + $(MAKE) install PREFIX=${stagedir} DOSTAGE=yes + +check: compile mkreports runtest + +mkreports: + -mkdir ${REPORTS} + +runtest: trio_test + ./trio_test ${REPORTS}/trio.xml + +trio_test: trio_test.cpp + ${CXX} -c ${CFLAGS} ${TEST_INC} $< + ${LINKXX} -o $@ trio_test.o ${LTLIB} ${TEST_LIBS} + + +dist: distsrc distbin + +distsrc: + mkdir -p ${top_srcdir}/${package}-${version} + cd ${top_srcdir} && GLOBIGNORE="${package}-${version}" && cp -Rf * ${package}-${version} + cd ${top_srcdir} && tar -czf ${distdir}/${package}-${version}_src.tar.gz --exclude-from=project/tar_exclude ${package}-${version} + rm -rf ${top_srcdir}/${package}-${version} + +distbin: + $(MAKE) install PREFIX=`pwd`/tmpbuilddir${stagedir} + save_dir=`pwd`; cd tmpbuilddir${stagedir} && tar -czf $$save_dir/${top_srcdir}/${distdir}/${package}-${version}_bin.tar.gz *; cd $$save_dir + rm -rf tmpbuilddir + +install: + mkdir -p ${PREFIX}/lib + mkdir -p ${PREFIX}/share/doc/${package}-${version} + ${INSTALL} -m 644 ${LTLIB} ${PREFIX}/lib + ${INSTALL} -m 644 ${top_srcdir}/LICENSE ${PREFIX}/share/doc/${package}-${version} + if [ x${DOSTAGE} = xyes ]; then \ + mkdir -p ${PREFIX}/include/${globalprefix}/${lbuprefix} ; \ + (cd ${top_srcdir}/interface && install -m 644 ${HDRS} ${PREFIX}/include/${globalprefix}/${lbuprefix}) ; \ + install -m 644 ${STATICLIB} ${PREFIX}/lib; \ + fi + +clean: + +%.o: %.c + ${COMPILE} ${CFLAGS} -c $< diff --git a/org.glite.lbjp-common.trio/interface/escape.h b/org.glite.lbjp-common.trio/interface/escape.h new file mode 100644 index 0000000..4795f68 --- /dev/null +++ b/org.glite.lbjp-common.trio/interface/escape.h @@ -0,0 +1,59 @@ +#ifndef __EDG_WORKLOAD_LOGGING_COMMON_ESCAPE_H__ +#define __EDG_WORKLOAD_LOGGING_COMMON_ESCAPE_H__ +/*! + * \file escape.h + */ + +#ident "$Header$" + + +/*! + * \fn char *glite_lbu_EscapeULM(const char *str) + * \param str a string to escape + * \return new (allocated) escaped string + * \brief in given string (ULM) escape all ULM_QM, ULM_BS and ULM_LF by ULM_BS + */ + +char *glite_lbu_EscapeULM(const char *); + + +/*! + * \fn char *glite_lbu_UnescapeULM(const char *str) + * \param str a string to unescape + * \return new (allocated) unescaped string + * \brief in given string (ULM) unescape all escaped ULM_QM, ULM_BS and ULM_LF + */ + +char *glite_lbu_UnescapeULM(const char *); + + +/*! + * \fn char *glite_lbu_EscapeXML(const char *str); + * \param str a string to escape + * \return new (allocated) escaped string + * \brief in given string (XML) escape all unwanted characters + */ + +char *glite_lbu_EscapeXML(const char *); + + +/*! + * \fn char *glite_lbu_UnescapeXML(const char *str) + * \param str a string to unescape + * \return new (allocated) unescaped string + * \brief in given string (XML) unescape all escaped characters + */ + +char *glite_lbu_UnescapeXML(const char *); + + +/*! + * \fn char *glite_lbu_EscapeSQL(const char *str) + * \param str a string to escape + * \return new (allocated) escaped string + * \briefin given string (SQL) escape all unwanted characters + */ + +char *glite_lbu_EscapeSQL(const char *); + +#endif /* __EDG_WORKLOAD_LOGGING_COMMON_ESCAPE_H__ */ diff --git a/org.glite.lbjp-common.trio/interface/trio.h b/org.glite.lbjp-common.trio/interface/trio.h new file mode 100644 index 0000000..04f133c --- /dev/null +++ b/org.glite.lbjp-common.trio/interface/trio.h @@ -0,0 +1,187 @@ +/************************************************************************* + * + * $Id$ + * + * Copyright (C) 1998 Bjorn Reese and Daniel Stenberg. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND + * CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. + * + ************************************************************************/ + +#ifndef TRIO_TRIO_H +#define TRIO_TRIO_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* make utility and C++ compiler in Windows NT fails to find this symbol */ +#if defined(WIN32) && !defined(isascii) +# define isascii ((unsigned)(x) < 0x80) +#endif + +/* + * Error codes. + * + * Remember to add a textual description to trio_strerror. + */ +enum { + TRIO_EOF = 1, + TRIO_EINVAL = 2, + TRIO_ETOOMANY = 3, + TRIO_EDBLREF = 4, + TRIO_EGAP = 5, + TRIO_ENOMEM = 6, + TRIO_ERANGE = 7 +}; + +/* Error macros */ +#define TRIO_ERROR_CODE(x) ((-(x)) & 0x00FF) +#define TRIO_ERROR_POSITION(x) ((-(x)) >> 8) +#define TRIO_ERROR_NAME(x) trio_strerror(x) + +const char *trio_strerror(int); + +/************************************************************************* + * Print Functions + */ + +int trio_printf(const char *format, ...); +int trio_vprintf(const char *format, va_list args); +int trio_printfv(const char *format, void **args); + +int trio_fprintf(FILE *file, const char *format, ...); +int trio_vfprintf(FILE *file, const char *format, va_list args); +int trio_fprintfv(FILE *file, const char *format, void **args); + +int trio_dprintf(int fd, const char *format, ...); +int trio_vdprintf(int fd, const char *format, va_list args); +int trio_dprintfv(int fd, const char *format, void **args); + +/* trio_sprintf(target, format, ...) + * trio_snprintf(target, maxsize, format, ...) + * + * Build 'target' according to 'format' and succesive + * arguments. This is equal to the sprintf() and + * snprintf() functions. + */ +int trio_sprintf(char *buffer, const char *format, ...); +int trio_vsprintf(char *buffer, const char *format, va_list args); +int trio_sprintfv(char *buffer, const char *format, void **args); + +int trio_snprintf(char *buffer, size_t max, const char *format, ...); +int trio_vsnprintf(char *buffer, size_t bufferSize, const char *format, + va_list args); +int trio_snprintfv(char *buffer, size_t bufferSize, const char *format, + void **args); + +int trio_snprintfcat(char *buffer, size_t max, const char *format, ...); +int trio_vsnprintfcat(char *buffer, size_t bufferSize, const char *format, + va_list args); + +char *trio_aprintf(const char *format, ...); +char *trio_vaprintf(const char *format, va_list args); + +int trio_asprintf(char **ret, const char *format, ...); +int trio_vasprintf(char **ret, const char *format, va_list args); + +/************************************************************************* + * Scan Functions + */ +int trio_scanf(const char *format, ...); +int trio_vscanf(const char *format, va_list args); +int trio_scanfv(const char *format, void **args); + +int trio_fscanf(FILE *file, const char *format, ...); +int trio_vfscanf(FILE *file, const char *format, va_list args); +int trio_fscanfv(FILE *file, const char *format, void **args); + +int trio_dscanf(int fd, const char *format, ...); +int trio_vdscanf(int fd, const char *format, va_list args); +int trio_dscanfv(int fd, const char *format, void **args); + +int trio_sscanf(const char *buffer, const char *format, ...); +int trio_vsscanf(const char *buffer, const char *format, va_list args); +int trio_sscanfv(const char *buffer, const char *format, void **args); + +/************************************************************************* + * Renaming + */ +#ifdef TRIO_REPLACE_STDIO +/* Replace the functions */ +#ifndef HAVE_PRINTF +# define printf trio_printf +#endif +#ifndef HAVE_VPRINTF +# define vprintf trio_vprintf +#endif +#ifndef HAVE_FPRINTF +# define fprintf trio_fprintf +#endif +#ifndef HAVE_VFPRINTF +# define vfprintf trio_vfprintf +#endif +#ifndef HAVE_SPRINTF +# define sprintf trio_sprintf +#endif +#ifndef HAVE_VSPRINTF +# define vsprintf trio_vsprintf +#endif +#ifndef HAVE_SNPRINTF +# define snprintf trio_snprintf +#endif +#ifndef HAVE_VSNPRINTF +# define vsnprintf trio_vsnprintf +#endif +#ifndef HAVE_SCANF +# define scanf trio_scanf +#endif +#ifndef HAVE_VSCANF +# define vscanf trio_vscanf +#endif +#ifndef HAVE_FSCANF +# define fscanf trio_fscanf +#endif +#ifndef HAVE_VFSCANF +# define vfscanf trio_vfscanf +#endif +#ifndef HAVE_SSCANF +# define sscanf trio_sscanf +#endif +#ifndef HAVE_VSSCANF +# define vsscanf trio_vsscanf +#endif +/* These aren't stdio functions, but we make them look similar */ +#define dprintf trio_dprintf +#define vdprintf trio_vdprintf +#define aprintf trio_aprintf +#define vaprintf trio_vaprintf +#define asprintf trio_asprintf +#define vasprintf trio_vasprintf +#define dscanf trio_dscanf +#define vdscanf trio_vdscanf +#endif + +/* strio compatible names */ +#define StrScan trio_sscanf +#define StrFormat trio_sprintf +#define StrFormatMax trio_snprintf +#define StrFormatAlloc trio_aprintf +#define StrFormatAppendMax trio_snprintfcat + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* TRIO_TRIO_H */ diff --git a/org.glite.lbjp-common.trio/src/escape.c b/org.glite.lbjp-common.trio/src/escape.c new file mode 100644 index 0000000..9a1d545 --- /dev/null +++ b/org.glite.lbjp-common.trio/src/escape.c @@ -0,0 +1,224 @@ +#ident "$Header$" + +#include +#include +#include +#include + +#include "escape.h" + +#define ULM_QM '"' +#define ULM_BS '\\' +#define ULM_LF '\n' + +/* + *---------------------------------------------------------------------- + * + * \fn char *glite_lbu_EscapeULM(const char *str) + * \param str a string to escape + * \return new (allocated) escaped string + * \brief in given string escape all ULM_QM, ULM_BS and ULM_LF by ULM_BS + * + * Calls: malloc, strlen + * + * Algorithm: array lookup + * - the new string will be allocated + * + *---------------------------------------------------------------------- + */ + +char *glite_lbu_EscapeULM(const char *str) +{ +unsigned int i,j; +size_t size; +char *ret; + +if (str == NULL) return NULL; +if ((size = strlen(str)) == 0) return strdup(""); + +ret = (char*) malloc(1+2*size*sizeof(char)); + +j = 0; +for (i=0; i',"gt" }, + { '&',"amp" }, + { '"',"quot" }, + { '\'',"apos" }, + { 0, NULL } +}; + +#define XML_ESCAPE_SET "<>&\"'" + +char *glite_lbu_EscapeXML(const char *in) +{ + const char* tmp_in; + char *out; + int cnt,i,j,k; + + if (!in) return NULL; + + for (cnt = 0, tmp_in = in; *tmp_in != '\0'; ++tmp_in) { + if (strchr(XML_ESCAPE_SET, *tmp_in) || + (*tmp_in & 0x7f) < 0x20 /* control character */ || + (*tmp_in == '%')) cnt++; + } + + out = malloc(strlen(in)+1+cnt*5); + + for (i=j=0; in[i]; i++) { + for (k=0; xml_etab[k].c && xml_etab[k].c != in[i]; k++); + if (xml_etab[k].c) { + int l; + + out[j++] = '&'; + memcpy(out+j,xml_etab[k].e,l=strlen(xml_etab[k].e)); + j += l; + out[j++] = ';'; + } else if ((in[i] & 0x7f) < 0x20 || in[i] == '%') { + sprintf(out+j, "%%%02x", (unsigned char)in[i]); + j+=3; + } else { + out[j++] = in[i]; + } + } + out[j] = 0; + return out; +} + +char *glite_lbu_UnescapeXML(const char *in) +{ + char *out; + int i,j,k; + char xtmp[3]; + unsigned char origchar; + + if (!in) return NULL; + out = malloc(strlen(in)+1); + + for (i=j=0; in[i]; j++) if (in[i] == '&') { + char *s = strchr(in+i,';'); + if (s) { + int l = s-in-i+1; + for (k=0; xml_etab[k].c && strncasecmp(in+i+1,xml_etab[k].e,l-2); k++); + if (xml_etab[k].c) { + out[j] = xml_etab[k].c; + i += l; + } else out[j] = in[i++]; + } else out[j] = in[i++]; + } else if (in[i] == '%') { + if (isxdigit(xtmp[0]=in[i+1]) && isxdigit(xtmp[1]=in[i+2])) { + xtmp[2] = '\0'; + origchar = (unsigned char) strtol(xtmp, NULL, 16); + if ((origchar & 0x7f) < 0x20 || origchar == '%') { + out[j] = origchar; + i += 3; + } else out[j] = in[i++]; + } else out[j] = in[i++]; + } else { + out[j] = in[i++]; + } + out[j] = 0; + return out; +} + +char *glite_lbu_EscapeSQL(const char *in) +{ + const char* tmp_in; + char *out = NULL; + int i,j,cnt; + + if (!in) return NULL; + + for (cnt = 0, tmp_in = in; (tmp_in = strchr(tmp_in,'\'')) != NULL; ++tmp_in) { + ++cnt; + } + for (tmp_in = in; (tmp_in = strchr(tmp_in,'\\')) != NULL; ++tmp_in) { + ++cnt; + } + + out = malloc(strlen(in)+1+cnt); + + for (i=j=0; in[i]; i++) { + if (in[i] == '\\') out[j++] = '\\'; + if (in[i] == '\'') out[j++] = '\''; + out[j++] = in[i]; + } + out[j] = 0; + + return out; +} diff --git a/org.glite.lbjp-common.trio/src/strio.c b/org.glite.lbjp-common.trio/src/strio.c new file mode 100644 index 0000000..f1ab5b8 --- /dev/null +++ b/org.glite.lbjp-common.trio/src/strio.c @@ -0,0 +1,581 @@ +/************************************************************************* + * + * $Id$ + * + * Copyright (C) 1998 Bjorn Reese and Daniel Stenberg. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND + * CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. + * + ************************************************************************/ + +/* + * TODO + * - StrToLongDouble + */ + +static const char rcsid[] = "@(#)$Id$"; + +#if defined(unix) || defined(__xlC__) || defined(__QNX__) +# define PLATFORM_UNIX +#elif defined(WIN32) || defined(_WIN32) +# define PLATFORM_WIN32 +#elif defined(AMIGA) && defined(__GNUC__) +# define PLATFORM_UNIX +#endif + +#if defined(__STDC__) && (__STDC_VERSION__ >= 199901L) +# define TRIO_C99 +#endif + +#include "strio.h" +#include +#include +#include +#include +#include +#include +#ifndef DEBUG +# define NDEBUG +#endif +#include + +#ifndef NULL +# define NULL 0 +#endif +#define NIL ((char)0) +#ifndef FALSE +# define FALSE (1 == 0) +# define TRUE (! FALSE) +#endif + +#define VALID(x) (NULL != (x)) +#define INVALID(x) (NULL == (x)) + +#if defined(PLATFORM_UNIX) +# define USE_STRCASECMP +# define USE_STRNCASECMP +# define USE_STRERROR +# if defined(__QNX__) +# define strcasecmp(x,y) stricmp(x,y) +# define strncasecmp(x,y,n) strnicmp(x,y,n) +# endif +#elif defined(PLATFORM_WIN32) +# define USE_STRCASECMP +# define strcasecmp(x,y) strcmpi(x,y) +#endif + +/************************************************************************* + * StrAppendMax + */ +char *StrAppendMax(char *target, size_t max, const char *source) +{ + assert(VALID(target)); + assert(VALID(source)); + assert(max > 0); + + max -= StrLength(target) + 1; + return (max > 0) ? strncat(target, source, max) : target; +} + +/************************************************************************* + * StrCopyMax + */ +char *StrCopyMax(char *target, size_t max, const char *source) +{ + assert(VALID(target)); + assert(VALID(source)); + assert(max > 0); /* Includes != 0 */ + + target = strncpy(target, source, max - 1); + target[max - 1] = (char)0; + return target; +} + +/************************************************************************* + * StrDuplicate + */ +char *StrDuplicate(const char *source) +{ + char *target; + + assert(VALID(source)); + + target = StrAlloc(StrLength(source) + 1); + if (target) + { + StrCopy(target, source); + } + return target; +} + +/************************************************************************* + * StrDuplicateMax + */ +char *StrDuplicateMax(const char *source, size_t max) +{ + char *target; + size_t len; + + assert(VALID(source)); + assert(max > 0); + + /* Make room for string plus a terminating zero */ + len = StrLength(source) + 1; + if (len > max) + { + len = max; + } + target = StrAlloc(len); + if (target) + { + StrCopyMax(target, len, source); + } + return target; +} + +/************************************************************************* + * StrEqual + */ +int StrEqual(const char *first, const char *second) +{ + assert(VALID(first)); + assert(VALID(second)); + + if (VALID(first) && VALID(second)) + { +#if defined(USE_STRCASECMP) + return (0 == strcasecmp(first, second)); +#else + while ((*first != NIL) && (*second != NIL)) + { + if (toupper(*first) != toupper(*second)) + { + break; + } + first++; + second++; + } + return ((*first == NIL) && (*second == NIL)); +#endif + } + return FALSE; +} + +/************************************************************************* + * StrEqualCase + */ +int StrEqualCase(const char *first, const char *second) +{ + assert(VALID(first)); + assert(VALID(second)); + + if (VALID(first) && VALID(second)) + { + return (0 == strcmp(first, second)); + } + return FALSE; +} + +/************************************************************************* + * StrEqualCaseMax + */ +int StrEqualCaseMax(const char *first, size_t max, const char *second) +{ + assert(VALID(first)); + assert(VALID(second)); + + if (VALID(first) && VALID(second)) + { + return (0 == strncmp(first, second, max)); + } + return FALSE; +} + +/************************************************************************* + * StrEqualLocale + */ +int StrEqualLocale(const char *first, const char *second) +{ + assert(VALID(first)); + assert(VALID(second)); + +#if defined(LC_COLLATE) + return (strcoll(first, second) == 0); +#else + return StrEqual(first, second); +#endif +} + +/************************************************************************* + * StrEqualMax + */ +int StrEqualMax(const char *first, size_t max, const char *second) +{ + assert(VALID(first)); + assert(VALID(second)); + + if (VALID(first) && VALID(second)) + { +#if defined(USE_STRNCASECMP) + return (0 == strncasecmp(first, second, max)); +#else + /* Not adequately tested yet */ + size_t cnt = 0; + while ((*first != NIL) && (*second != NIL) && (cnt <= max)) + { + if (toupper(*first) != toupper(*second)) + { + break; + } + first++; + second++; + cnt++; + } + return ((cnt == max) || ((*first == NIL) && (*second == NIL))); +#endif + } + return FALSE; +} + +/************************************************************************* + * StrError + */ +const char *StrError(int errorNumber) +{ +#if defined(USE_STRERROR) + return strerror(errorNumber); +#else + return "unknown"; +#endif +} + +/************************************************************************* + * StrFormatDate + */ +size_t StrFormatDateMax(char *target, + size_t max, + const char *format, + const struct tm *datetime) +{ + assert(VALID(target)); + assert(VALID(format)); + assert(VALID(datetime)); + assert(max > 0); + + return strftime(target, max, format, datetime); +} + +/************************************************************************* + * StrHash + */ +unsigned long StrHash(const char *string, int type) +{ + unsigned long value = 0L; + char ch; + + assert(VALID(string)); + + switch (type) + { + case STRIO_HASH_PLAIN: + while ( (ch = *string++) != NIL ) + { + value *= 31; + value += (unsigned long)ch; + } + break; + default: + assert(FALSE); + break; + } + return value; +} + +/************************************************************************* + * StrMatch + */ +int StrMatch(char *string, char *pattern) +{ + assert(VALID(string)); + assert(VALID(pattern)); + + for (; ('*' != *pattern); ++pattern, ++string) + { + if (NIL == *string) + { + return (NIL == *pattern); + } + if ((toupper((int)*string) != toupper((int)*pattern)) + && ('?' != *pattern)) + { + return FALSE; + } + } + /* two-line patch to prevent *too* much recursiveness: */ + while ('*' == pattern[1]) + pattern++; + + do + { + if ( StrMatch(string, &pattern[1]) ) + { + return TRUE; + } + } + while (*string++); + + return FALSE; +} + +/************************************************************************* + * StrMatchCase + */ +int StrMatchCase(char *string, char *pattern) +{ + assert(VALID(string)); + assert(VALID(pattern)); + + for (; ('*' != *pattern); ++pattern, ++string) + { + if (NIL == *string) + { + return (NIL == *pattern); + } + if ((*string != *pattern) + && ('?' != *pattern)) + { + return FALSE; + } + } + /* two-line patch to prevent *too* much recursiveness: */ + while ('*' == pattern[1]) + pattern++; + + do + { + if ( StrMatchCase(string, &pattern[1]) ) + { + return TRUE; + } + } + while (*string++); + + return FALSE; +} + +/************************************************************************* + * StrSpanFunction + * + * Untested + */ +size_t StrSpanFunction(char *source, int (*Function)(int)) +{ + size_t count = 0; + + assert(VALID(source)); + assert(VALID(Function)); + + while (*source != NIL) + { + if (Function(*source)) + break; /* while */ + source++; + count++; + } + return count; +} + +/************************************************************************* + * StrSubstringMax + */ +char *StrSubstringMax(const char *string, size_t max, const char *find) +{ + size_t count; + size_t size; + char *result = NULL; + + assert(VALID(string)); + assert(VALID(find)); + + size = StrLength(find); + if (size <= max) + { + for (count = 0; count <= max - size; count++) + { + if (StrEqualMax(find, size, &string[count])) + { + result = (char *)&string[count]; + break; + } + } + } + return result; +} + +/************************************************************************* + * StrToDouble + * + * double ::= [ ] + * ( | + * | + * ) + * [ [ ] ] + * number ::= 1*( ) + * digit ::= ( '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' ) + * exponential ::= ( 'e' | 'E' ) + * sign ::= ( '-' | '+' ) + * decimal_point ::= '.' + */ +double StrToDouble(const char *source, const char **endp) +{ +#if defined(TRIO_C99) + return strtod(source, endp); +#else + /* Preliminary code */ + int isNegative = FALSE; + int isExponentNegative = FALSE; + unsigned long integer = 0; + unsigned long fraction = 0; + unsigned long fracdiv = 1; + unsigned long exponent = 0; + double value = 0.0; + + /* First try hex-floats */ + if ((source[0] == '0') && ((source[1] == 'x') || (source[1] == 'X'))) + { + source += 2; + while (isxdigit((int)*source)) + { + integer *= 16; + integer += (isdigit((int)*source) + ? (*source - '0') + : 10 + (toupper((int)*source) - 'A')); + source++; + } + if (*source == '.') + { + source++; + while (isxdigit((int)*source)) + { + fraction *= 16; + fraction += (isdigit((int)*source) + ? (*source - '0') + : 10 + (toupper((int)*source) - 'A')); + fracdiv *= 16; + source++; + } + if ((*source == 'p') || (*source == 'P')) + { + source++; + if ((*source == '+') || (*source == '-')) + { + isExponentNegative = (*source == '-'); + source++; + } + while (isdigit((int)*source)) + { + exponent *= 10; + exponent += (*source - '0'); + source++; + } + } + } + } + else /* Then try normal decimal floats */ + { + isNegative = (*source == '-'); + /* Skip sign */ + if ((*source == '+') || (*source == '-')) + source++; + + /* Integer part */ + while (isdigit((int)*source)) + { + integer *= 10; + integer += (*source - '0'); + source++; + } + + if (*source == '.') + { + source++; /* skip decimal point */ + while (isdigit((int)*source)) + { + fraction *= 10; + fraction += (*source - '0'); + fracdiv *= 10; + source++; + } + } + if ((*source == 'e') || (*source == 'E')) + { + source++; /* Skip exponential indicator */ + isExponentNegative = (*source == '-'); + if ((*source == '+') || (*source == '-')) + source++; + while (isdigit((int)*source)) + { + exponent *= 10; + exponent += (*source - '0'); + source++; + } + } + } + + value = (double)integer; + if (fraction != 0) + { + value += (double)fraction / (double)fracdiv; + } + if (exponent != 0) + { + if (isExponentNegative) + value /= pow((double)10, (double)exponent); + else + value *= pow((double)10, (double)exponent); + } + if (isNegative) + value = -value; + + if (endp) + *endp = source; + return value; +#endif +} + +/************************************************************************* + * StrToFloat + */ +float StrToFloat(const char *source, const char **endp) +{ +#if defined(TRIO_C99) + return strtof(source, endp); +#else + return (float)StrToDouble(source, endp); +#endif +} + +/************************************************************************* + * StrToUpper + */ +int StrToUpper(char *target) +{ + int i = 0; + + assert(VALID(target)); + + while (NIL != *target) + { + *target = toupper((int)*target); + target++; + i++; + } + return i; +} diff --git a/org.glite.lbjp-common.trio/src/strio.h b/org.glite.lbjp-common.trio/src/strio.h new file mode 100644 index 0000000..68845a3 --- /dev/null +++ b/org.glite.lbjp-common.trio/src/strio.h @@ -0,0 +1,227 @@ +/************************************************************************* + * + * $Id$ + * + * Copyright (C) 1998 Bjorn Reese and Daniel Stenberg. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND + * CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. + * + ************************************************************************/ + +#ifndef TRIO_STRIO_H +#define TRIO_STRIO_H + +#if !(defined(DEBUG) || defined(NDEBUG)) +# define NDEBUG +#endif +#include +#include +#include +#include + +#ifndef STRIO_MALLOC +# define STRIO_MALLOC(n) malloc(n) +#endif +#ifndef STRIO_FREE +# define STRIO_FREE(x) free(x) +#endif + +/* + * StrAppend(target, source) + * StrAppendMax(target, maxsize, source) + * + * Append 'source' to 'target' + * + * target = StrAlloc(size) + * + * Allocate a new string + * + * StrContains(target, substring) + * + * Find out if the string 'substring' is + * contained in the string 'target' + * + * StrCopy(target, source) + * StrCopyMax(target, maxsize, source) + * + * Copy 'source' to 'target' + * + * target = StrDuplicate(source) + * target = StrDuplicateMax(source, maxsize) + * + * Allocate and copy 'source' to 'target' + * + * StrEqual(first, second) + * StrEqualMax(first, maxsize, second) + * + * Compare if 'first' is equal to 'second'. + * Case-independent. + * + * StrEqualCase(first, second) + * StrEqualCaseMax(first, maxsize, second) + * + * Compare if 'first' is equal to 'second' + * Case-dependent. Please note that the use of the + * word 'case' has the opposite meaning as that of + * strcasecmp(). + * + * StrFormat(target, format, ...) + * StrFormatMax(target, maxsize, format, ...) + * + * Build 'target' according to 'format' and succesive + * arguments. This is equal to the sprintf() and + * snprintf() functions. + * + * StrFormatDate(target, format, ...) + * + * StrFree(target) + * + * De-allocates a string + * + * StrHash(string, type) + * + * Calculates the hash value of 'string' based on the + * 'type'. + * + * StrIndex(target, character) + * StrIndexLast(target, character) + * + * Find the first/last occurrence of 'character' in + * 'target' + * + * StrLength(target) + * + * Return the length of 'target' + * + * StrMatch(string, pattern) + * StrMatchCase(string, pattern) + * + * Find 'pattern' within 'string'. 'pattern' may contain + * wildcards such as * (asterics) and ? (question mark) + * which matches zero or more characters and exactly + * on character respectively + * + * StrScan(source, format, ...) + * + * Equal to sscanf() + * + * StrSubstring(target, substring) + * + * Find the first occurrence of the string 'substring' + * within the string 'target' + * + * StrTokenize(target, list) + * + * Split 'target' into the first token delimited by + * one of the characters in 'list'. If 'target' is + * NULL then next token will be returned. + * + * StrToUpper(target) + * + * Convert all lower case characters in 'target' into + * upper case characters. + */ + +enum { + STRIO_HASH_NONE = 0, + STRIO_HASH_PLAIN, + STRIO_HASH_TWOSIGNED +}; + +#if !defined(DEBUG) || defined(__DECC) +#define StrAlloc(n) (char *)STRIO_MALLOC(n) +#define StrAppend(x,y) strcat((x), (y)) +#define StrContains(x,y) (0 != strstr((x), (y))) +#define StrCopy(x,y) strcpy((x), (y)) +#define StrIndex(x,y) strchr((x), (y)) +#define StrIndexLast(x,y) strrchr((x), (y)) +#define StrFree(x) STRIO_FREE(x) +#define StrLength(x) strlen((x)) +#define StrSubstring(x,y) strstr((x), (y)) +#define StrTokenize(x,y) strtok((x), (y)) +#define StrToLong(x,y,n) strtol((x), (y), (n)) +#define StrToUnsignedLong(x,y,n) strtoul((x), (y), (n)) +#else /* DEBUG */ + /* + * To be able to use these macros everywhere, including in + * if() sentences, the assertions are put first in a comma + * seperated list. + * + * Unfortunately the DECC compiler does not seem to like this + * so it will use the un-asserted functions above for the + * debugging case too. + */ +#define StrAlloc(n) \ + (assert((n) > 0),\ + (char *)STRIO_MALLOC(n)) +#define StrAppend(x,y) \ + (assert((x) != NULL),\ + assert((y) != NULL),\ + strcat((x), (y))) +#define StrContains(x,y) \ + (assert((x) != NULL),\ + assert((y) != NULL),\ + (0 != strstr((x), (y)))) +#define StrCopy(x,y) \ + (assert((x) != NULL),\ + assert((y) != NULL),\ + strcpy((x), (y))) +#define StrIndex(x,c) \ + (assert((x) != NULL),\ + strchr((x), (c))) +#define StrIndexLast(x,c) \ + (assert((x) != NULL),\ + strrchr((x), (c))) +#define StrFree(x) \ + (assert((x) != NULL),\ + STRIO_FREE(x)) +#define StrLength(x) \ + (assert((x) != NULL),\ + strlen((x))) +#define StrSubstring(x,y) \ + (assert((x) != NULL),\ + assert((y) != NULL),\ + strstr((x), (y))) +#define StrTokenize(x,y) \ + (assert((y) != NULL),\ + strtok((x), (y))) +#define StrToLong(x,y,n) \ + (assert((x) != NULL),\ + assert((y) != NULL),\ + assert((n) >= 2 && (n) <= 36),\ + strtol((x), (y), (n))) +#define StrToUnsignedLong(x,y,n) \ + (assert((x) != NULL),\ + assert((y) != NULL),\ + assert((n) >= 2 && (n) <= 36),\ + strtoul((x), (y), (n))) +#endif /* DEBUG */ + +char *StrAppendMax(char *target, size_t max, const char *source); +char *StrCopyMax(char *target, size_t max, const char *source); +char *StrDuplicate(const char *source); +char *StrDuplicateMax(const char *source, size_t max); +int StrEqual(const char *first, const char *second); +int StrEqualCase(const char *first, const char *second); +int StrEqualCaseMax(const char *first, size_t max, const char *second); +int StrEqualLocale(const char *first, const char *second); +int StrEqualMax(const char *first, size_t max, const char *second); +const char *StrError(int); +size_t StrFormatDateMax(char *target, size_t max, const char *format, const struct tm *datetime); +unsigned long StrHash(const char *string, int type); +int StrMatch(char *string, char *pattern); +int StrMatchCase(char *string, char *pattern); +size_t StrSpanFunction(char *source, int (*Function)(int)); +char *StrSubstringMax(const char *string, size_t max, const char *find); +float StrToFloat(const char *source, const char **target); +double StrToDouble(const char *source, const char **target); +int StrToUpper(char *target); + +#endif /* TRIO_STRIO_H */ diff --git a/org.glite.lbjp-common.trio/src/trio.c b/org.glite.lbjp-common.trio/src/trio.c new file mode 100644 index 0000000..6e4211e --- /dev/null +++ b/org.glite.lbjp-common.trio/src/trio.c @@ -0,0 +1,5706 @@ + +/************************************************************************* + * + * $Id$ + * + * Copyright (C) 1998 Bjorn Reese and Daniel Stenberg. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND + * CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. + * + ************************************************************************* + * + * A note to trio contributors: + * + * Avoid heap allocation at all costs to ensure that the trio functions + * are async-safe. The exceptions are the printf/fprintf functions, which + * uses fputc, and the asprintf functions and the modifier, which + * by design are required to allocate form the heap. + * + ************************************************************************/ + +/* + * TODO: + * - Scan is probably too permissive about its modifiers. + * - C escapes in %#[] ? + * - C99 support has not been properly tested. + * - Multibyte characters (done for format parsing, except scan groups) + * - Complex numbers? (C99 _Complex) + * - Boolean values? (C99 _Bool) + * - C99 NaN(n-char-sequence) missing + * - Should we support the GNU %a alloc modifier? GNU has an ugly hack + * for %a, because C99 used %a for other purposes. If specified as + * %as or %a[ it is interpreted as the alloc modifier, otherwise as + * the C99 hex-float. This means that you cannot scan %as as a hex-float + * immediately followed by an 's'. + * - Scanning of collating symbols. + */ + +static const char rcsid[] = "@(#)$Id$"; + +/************************************************************************* + * Trio include files + */ +#include "trio.h" +#include "triop.h" +#include "strio.h" + +#ifdef DATAGRID_EXTENSION +#include "escape.h" +#endif + +/* + * Encode the error code and the position. This is decoded + * with TRIO_ERROR_CODE and TRIO_ERROR_POSITION. + */ +#if TRIO_ERRORS +# define TRIO_ERROR_RETURN(x,y) (- ((x) + ((y) << 8))) +#else +# define TRIO_ERROR_RETURN(x,y) (-1) +#endif + + +/************************************************************************* + * Platform and compiler support detection + */ +#if defined(unix) || defined(__xlC__) || defined(_AIX) || defined(__QNX__) +# define PLATFORM_UNIX +#elif defined(AMIGA) && defined(__GNUC__) +# define PLATFORM_UNIX +#elif defined(WIN32) || defined(_WIN32) || defined(_MSC_VER) +# define PLATFORM_WIN32 +# define TRIO_MSVC_5 1100 +#endif + +#if defined(__STDC__) && defined(__STDC_VERSION__) +# if (__STDC_VERSION__ >= 199409L) +# define TRIO_COMPILER_SUPPORTS_ISO94 +# endif +# if (__STDC_VERSION__ >= 199901L) +# define TRIO_COMPILER_SUPPORTS_C99 +# endif +#endif + +#if defined(_XOPEN_SOURCE) && defined(_XOPEN_SOURCE_EXTENDED) +# define TRIO_COMPILER_SUPPORTS_UNIX98 +#endif + +#if defined(__STDC_ISO_10646__) || defined(MB_LEN_MAX) || defined(USE_MULTIBYTE) || TRIO_WIDECHAR +# define TRIO_COMPILER_SUPPORTS_MULTIBYTE +# if !defined(MB_LEN_MAX) +# define MB_LEN_MAX 6 +# endif +#endif + + +/************************************************************************* + * Generic definitions + */ + +#if !(defined(DEBUG) || defined(NDEBUG)) +# define NDEBUG +#endif +#include +#include +#if !defined(TRIO_COMPILER_SUPPORTS_C99) && !defined(isblank) +# define isblank(x) (((x)==32) || ((x)==9)) +#endif +#include +#include +#include +#include +#include +#include + +#ifndef NULL +# define NULL 0 +#endif +#define NIL ((char)0) +#ifndef FALSE +# define FALSE (1 == 0) +# define TRUE (! FALSE) +#endif +#define BOOLEAN_T int + +/* mincore() can be used for debugging purposes */ +#define VALID(x) (NULL != (x)) + +/* xlC crashes on log10(0) */ +#define guarded_log10(x) (((x) == 0.0) ? -HUGE_VAL : log10(x)) +#define guarded_log16(x) (guarded_log10(x) / log10(16.0)) + + +/************************************************************************* + * Platform specific definitions + */ +#if defined(PLATFORM_UNIX) +# include +# include +# include +# define USE_LOCALE +#endif /* PLATFORM_UNIX */ +#if defined(PLATFORM_WIN32) +# include +# define read _read +# define write _write +#endif /* PLATFORM_WIN32 */ + +#if TRIO_WIDECHAR +# if defined(TRIO_COMPILER_SUPPORTS_ISO94) +# include +# include +# else +typedef char wchar_t; +typedef int wint_t; +# define WEOF EOF +# define iswalnum(x) isalnum(x) +# define iswalpha(x) isalpha(x) +# define iswblank(x) isblank(x) +# define iswcntrl(x) iscntrl(x) +# define iswdigit(x) isdigit(x) +# define iswgraph(x) isgraph(x) +# define iswlower(x) islower(x) +# define iswprint(x) isprint(x) +# define iswpunct(x) ispunct(x) +# define iswspace(x) isspace(x) +# define iswupper(x) isupper(x) +# define iswxdigit(x) isxdigit(x) +# endif +#endif + + +/************************************************************************* + * Compiler dependent definitions + */ + +/* Support for long long */ +#ifndef __cplusplus +# if !defined(USE_LONGLONG) +# if defined(__GNUC__) && !defined(__STRICT_ANSI__) +# define USE_LONGLONG +# elif defined(__SUNPRO_C) +# define USE_LONGLONG +# elif defined(_LONG_LONG) || defined(_LONGLONG) +# define USE_LONGLONG +# endif +# endif +#endif + +/* The extra long numbers */ +#if defined(USE_LONGLONG) +typedef signed long long int trio_longlong_t; +typedef unsigned long long int trio_ulonglong_t; +#elif defined(_MSC_VER) +# if (_MSC_VER >= TRIO_MSVC_5) +typedef signed __int64 trio_longlong_t; +typedef unsigned __int64 trio_ulonglong_t; +# else +typedef signed long int trio_longlong_t; +typedef unsigned long int trio_ulonglong_t; +# endif +#else +typedef signed long int trio_longlong_t; +typedef unsigned long int trio_ulonglong_t; +#endif + +/* Maximal and fixed integer types */ +#if defined(TRIO_COMPILER_SUPPORTS_C99) +# include +typedef intmax_t trio_intmax_t; +typedef uintmax_t trio_uintmax_t; +typedef int8_t trio_int8_t; +typedef int16_t trio_int16_t; +typedef int32_t trio_int32_t; +typedef int64_t trio_int64_t; +#elif defined(TRIO_COMPILER_SUPPORTS_UNIX98) +# include +typedef intmax_t trio_intmax_t; +typedef uintmax_t trio_uintmax_t; +typedef int8_t trio_int8_t; +typedef int16_t trio_int16_t; +typedef int32_t trio_int32_t; +typedef int64_t trio_int64_t; +#elif defined(_MSC_VER) && (_MSC_VER >= TRIO_MSVC_5) +typedef trio_longlong_t trio_intmax_t; +typedef trio_ulonglong_t trio_uintmax_t; +typedef __int8 trio_int8_t; +typedef __int16 trio_int16_t; +typedef __int32 trio_int32_t; +typedef __int64 trio_int64_t; +#else +typedef trio_longlong_t trio_intmax_t; +typedef trio_ulonglong_t trio_uintmax_t; +# if defined(TRIO_INT8_T) +typedef TRIO_INT8_T trio_int8_t; +# else +typedef signed char trio_int8_t; +# endif +# if defined(TRIO_INT16_T) +typedef TRIO_INT16_T trio_int16_t; +# else +typedef signed short trio_int16_t; +# endif +# if defined(TRIO_INT32_T) +typedef TRIO_INT32_T trio_int32_t; +# else +typedef signed int trio_int32_t; +# endif +# if defined(TRIO_INT64_T) +typedef TRIO_INT64_T trio_int64_t; +# else +typedef trio_longlong_t trio_int64_t; +# endif +#endif + + +/************************************************************************* + * Internal definitions + */ + +/* Long double sizes */ +#ifdef LDBL_DIG +# define MAX_MANTISSA_DIGITS LDBL_DIG +# define MAX_EXPONENT_DIGITS 4 +#else +# define MAX_MANTISSA_DIGITS DBL_DIG +# define MAX_EXPONENT_DIGITS 3 +#endif + +/* The maximal number of digits is for base 2 */ +#define MAX_CHARS_IN(x) (sizeof(x) * CHAR_BIT) +/* The width of a pointer. The number of bits in a hex digit is 4 */ +#define POINTER_WIDTH ((sizeof("0x") - 1) + sizeof(void *) * CHAR_BIT / 4) + +/* Infinite and Not-A-Number for floating-point */ +#define INFINITE_LOWER "inf" +#define INFINITE_UPPER "INF" +#define LONG_INFINITE_LOWER "infinite" +#define LONG_INFINITE_UPPER "INFINITE" +#define NAN_LOWER "nan" +#define NAN_UPPER "NAN" + +/* Various constants */ +enum { + TYPE_PRINT = 1, + TYPE_SCAN = 2, + + /* Flags. Use maximum 32 */ + FLAGS_NEW = 0, + FLAGS_STICKY = 1, + FLAGS_SPACE = 2 * FLAGS_STICKY, + FLAGS_SHOWSIGN = 2 * FLAGS_SPACE, + FLAGS_LEFTADJUST = 2 * FLAGS_SHOWSIGN, + FLAGS_ALTERNATIVE = 2 * FLAGS_LEFTADJUST, + FLAGS_SHORT = 2 * FLAGS_ALTERNATIVE, + FLAGS_SHORTSHORT = 2 * FLAGS_SHORT, + FLAGS_LONG = 2 * FLAGS_SHORTSHORT, + FLAGS_QUAD = 2 * FLAGS_LONG, + FLAGS_LONGDOUBLE = 2 * FLAGS_QUAD, + FLAGS_SIZE_T = 2 * FLAGS_LONGDOUBLE, + FLAGS_PTRDIFF_T = 2 * FLAGS_SIZE_T, + FLAGS_INTMAX_T = 2 * FLAGS_PTRDIFF_T, + FLAGS_NILPADDING = 2 * FLAGS_INTMAX_T, + FLAGS_UNSIGNED = 2 * FLAGS_NILPADDING, + FLAGS_UPPER = 2 * FLAGS_UNSIGNED, + FLAGS_WIDTH = 2 * FLAGS_UPPER, + FLAGS_WIDTH_PARAMETER = 2 * FLAGS_WIDTH, + FLAGS_PRECISION = 2 * FLAGS_WIDTH_PARAMETER, + FLAGS_PRECISION_PARAMETER = 2 * FLAGS_PRECISION, + FLAGS_BASE = 2 * FLAGS_PRECISION_PARAMETER, + FLAGS_BASE_PARAMETER = 2 * FLAGS_BASE, + FLAGS_FLOAT_E = 2 * FLAGS_BASE_PARAMETER, + FLAGS_FLOAT_G = 2 * FLAGS_FLOAT_E, + FLAGS_QUOTE = 2 * FLAGS_FLOAT_G, + FLAGS_WIDECHAR = 2 * FLAGS_QUOTE, + FLAGS_ALLOC = 2 * FLAGS_WIDECHAR, + FLAGS_IGNORE = 2 * FLAGS_ALLOC, + FLAGS_IGNORE_PARAMETER = 2 * FLAGS_IGNORE, + FLAGS_VARSIZE_PARAMETER = 2 * FLAGS_IGNORE_PARAMETER, + FLAGS_FIXED_SIZE = 2 * FLAGS_VARSIZE_PARAMETER, + /* Reused flags */ + FLAGS_EXCLUDE = FLAGS_SHORT, + FLAGS_USER_DEFINED = FLAGS_IGNORE, + /* Compounded flags */ + FLAGS_ALL_VARSIZES = FLAGS_LONG | FLAGS_QUAD | FLAGS_INTMAX_T | FLAGS_PTRDIFF_T | FLAGS_SIZE_T, + FLAGS_ALL_SIZES = FLAGS_ALL_VARSIZES | FLAGS_SHORTSHORT | FLAGS_SHORT, + + NO_POSITION = -1, + NO_WIDTH = 0, + NO_PRECISION = -1, + NO_SIZE = -1, + + NO_BASE = -1, + MIN_BASE = 2, + MAX_BASE = 36, + BASE_BINARY = 2, + BASE_OCTAL = 8, + BASE_DECIMAL = 10, + BASE_HEX = 16, + + /* Maximal number of allowed parameters */ + MAX_PARAMETERS = 64, + /* Maximal number of characters in class */ + MAX_CHARACTER_CLASS = UCHAR_MAX, + + /* Maximal string lengths for user-defined specifiers */ + MAX_USER_NAME = 64, + MAX_USER_DATA = 256, + + /* Maximal length of locale separator strings */ + MAX_LOCALE_SEPARATOR_LENGTH = MB_LEN_MAX, + /* Maximal number of integers in grouping */ + MAX_LOCALE_GROUPS = 64 +}; + +#define NO_GROUPING ((int)CHAR_MAX) + +/* Fundamental formatting parameter types */ +#define FORMAT_UNKNOWN 0 +#define FORMAT_INT 1 +#define FORMAT_DOUBLE 2 +#define FORMAT_CHAR 3 +#define FORMAT_STRING 4 +#define FORMAT_POINTER 5 +#define FORMAT_COUNT 6 +#define FORMAT_PARAMETER 7 +#define FORMAT_GROUP 8 +#if TRIO_GNU +# define FORMAT_ERRNO 9 +#endif +#if TRIO_EXTENSION +# define FORMAT_USER_DEFINED 10 +#endif + +/* Character constants */ +#define CHAR_IDENTIFIER '%' +#define CHAR_BACKSLASH '\\' +#define CHAR_QUOTE '\"' +#define CHAR_ADJUST ' ' + +/* Character class expressions */ +#define CLASS_ALNUM ":alnum:" +#define CLASS_ALPHA ":alpha:" +#define CLASS_CNTRL ":cntrl:" +#define CLASS_DIGIT ":digit:" +#define CLASS_GRAPH ":graph:" +#define CLASS_LOWER ":lower:" +#define CLASS_PRINT ":print:" +#define CLASS_PUNCT ":punct:" +#define CLASS_SPACE ":space:" +#define CLASS_UPPER ":upper:" +#define CLASS_XDIGIT ":xdigit:" + +/* + * SPECIFIERS: + * + * + * a Hex-float + * A Hex-float + * c Character + * C Widechar character (wint_t) + * d Decimal + * e Float + * E Float + * F Float + * F Float + * g Float + * G Float + * i Integer + * m Error message + * n Count + * o Octal + * p Pointer + * s String + * S Widechar string (wchar_t *) + * u Unsigned + * x Hex + * X Hex + * [] Group + * <> User-defined + * + * Reserved: + * + * D Binary Coded Decimal %D(length,precision) (OS/390) + */ +#define SPECIFIER_CHAR 'c' +#define SPECIFIER_STRING 's' +#define SPECIFIER_DECIMAL 'd' +#define SPECIFIER_INTEGER 'i' +#define SPECIFIER_UNSIGNED 'u' +#define SPECIFIER_OCTAL 'o' +#define SPECIFIER_HEX 'x' +#define SPECIFIER_HEX_UPPER 'X' +#define SPECIFIER_FLOAT_E 'e' +#define SPECIFIER_FLOAT_E_UPPER 'E' +#define SPECIFIER_FLOAT_F 'f' +#define SPECIFIER_FLOAT_F_UPPER 'F' +#define SPECIFIER_FLOAT_G 'g' +#define SPECIFIER_FLOAT_G_UPPER 'G' +#define SPECIFIER_POINTER 'p' +#define SPECIFIER_GROUP '[' +#define SPECIFIER_UNGROUP ']' +#define SPECIFIER_COUNT 'n' +#if TRIO_UNIX98 +# define SPECIFIER_CHAR_UPPER 'C' +# define SPECIFIER_STRING_UPPER 'S' +#endif +#if TRIO_C99 +# define SPECIFIER_HEXFLOAT 'a' +# define SPECIFIER_HEXFLOAT_UPPER 'A' +#endif +#if TRIO_GNU +# define SPECIFIER_ERRNO 'm' +#endif +#if TRIO_EXTENSION +# define SPECIFIER_BINARY 'b' +# define SPECIFIER_BINARY_UPPER 'B' +# define SPECIFIER_USER_DEFINED_BEGIN '<' +# define SPECIFIER_USER_DEFINED_END '>' +# define SPECIFIER_USER_DEFINED_SEPARATOR ':' +#endif + +/* + * QUALIFIERS: + * + * + * Numbers = d,i,o,u,x,X + * Float = a,A,e,E,f,F,g,G + * String = s + * Char = c + * + * + * 9$ Position + * Use the 9th parameter. 9 can be any number between 1 and + * the maximal argument + * + * 9 Width + * Set width to 9. 9 can be any number, but must not be postfixed + * by '$' + * + * h Short + * Numbers: + * (unsigned) short int + * + * hh Short short + * Numbers: + * (unsigned) char + * + * l Long + * Numbers: + * (unsigned) long int + * String: + * as the S specifier + * Char: + * as the C specifier + * + * ll Long Long + * Numbers: + * (unsigned) long long int + * + * L Long Double + * Float + * long double + * + * # Alternative + * Float: + * Decimal-point is always present + * String: + * non-printable characters are handled as \number + * + * Spacing + * + * + Sign + * + * - Alignment + * + * . Precision + * + * * Parameter + * print: use parameter + * scan: no parameter (ignore) + * + * q Quad + * + * Z size_t + * + * w Widechar + * + * ' Thousands/quote + * Numbers: + * Integer part grouped in thousands + * Binary numbers: + * Number grouped in nibbles (4 bits) + * String: + * Quoted string + * + * j intmax_t + * t prtdiff_t + * z size_t + * + * ! Sticky + * @ Parameter (for both print and scan) + * + * I n-bit Integer + * Numbers: + * The following options exists + * I8 = 8-bit integer + * I16 = 16-bit integer + * I32 = 32-bit integer + * I64 = 64-bit integer + */ +#define QUALIFIER_POSITION '$' +#define QUALIFIER_SHORT 'h' +#define QUALIFIER_LONG 'l' +#define QUALIFIER_LONG_UPPER 'L' +#define QUALIFIER_ALTERNATIVE '#' +#define QUALIFIER_SPACE ' ' +#define QUALIFIER_PLUS '+' +#define QUALIFIER_MINUS '-' +#define QUALIFIER_DOT '.' +#define QUALIFIER_STAR '*' +#define QUALIFIER_CIRCUMFLEX '^' +#if TRIO_C99 +# define QUALIFIER_SIZE_T 'z' +# define QUALIFIER_PTRDIFF_T 't' +# define QUALIFIER_INTMAX_T 'j' +#endif +#if TRIO_BSD || TRIO_GNU +# define QUALIFIER_QUAD 'q' +#endif +#if TRIO_GNU +# define QUALIFIER_SIZE_T_UPPER 'Z' +#endif +#if TRIO_MISC +# define QUALIFIER_WIDECHAR 'w' +#endif +#if TRIO_MICROSOFT +# define QUALIFIER_FIXED_SIZE 'I' +#endif +#if TRIO_EXTENSION +# define QUALIFIER_QUOTE '\'' +# define QUALIFIER_STICKY '!' +# define QUALIFIER_VARSIZE '&' /* This should remain undocumented */ +# define QUALIFIER_PARAM '@' /* Experimental */ +# define QUALIFIER_COLON ':' /* For scanlists */ +# define QUALIFIER_EQUAL '=' /* For scanlists */ +#endif +#if DATAGRID_EXTENSION +# define QUALIFIER_ESCAPE '|' +#endif + + +/************************************************************************* + * Internal structures + */ + +/* Parameters */ +typedef struct { + int type; + unsigned long flags; + int width; + int precision; + int base; + int varsize; +#ifdef QUALIFIER_ESCAPE + enum dg_escape { ESCAPE_NONE, ESCAPE_ULM, ESCAPE_XML, ESCAPE_SQL } escape; +#endif + int indexAfterSpecifier; + union { + char *string; +#if TRIO_WIDECHAR + wchar_t *wstring; +#endif + void *pointer; + union { + trio_uintmax_t as_signed; + trio_intmax_t as_unsigned; + } number; + double doubleNumber; + double *doublePointer; + long double longdoubleNumber; + long double *longdoublePointer; + int errorNumber; + } data; + /* For the user-defined specifier */ + char user_name[MAX_USER_NAME]; + char user_data[MAX_USER_DATA]; +} parameter_T; + +/* General trio "class" */ +typedef struct _trio_T { + void *location; + void (*OutStream)(struct _trio_T *, int); + void (*InStream)(struct _trio_T *, int *); + /* + * The number of characters that would have been written/read if + * there had been sufficient space. + */ + int processed; + /* + * The number of characters that are actually written/read. + * Processed and committed with only differ for the *nprintf + * and *nscanf functions. + */ + int committed; + int max; + int current; +} trio_T; + +/* References (for user-defined callbacks) */ +typedef struct _reference_T { + trio_T *data; + parameter_T *parameter; +} reference_T; + +/* Registered entries (for user-defined callbacks) */ +typedef struct _userdef_T { + struct _userdef_T *next; + trio_callback_t callback; + char *name; +} userdef_T; + + +/************************************************************************* + * Internal variables + */ + +static const char null[] = "(nil)"; + +#if defined(USE_LOCALE) +static struct lconv *internalLocaleValues = NULL; +#endif + +/* + * UNIX98 says "in a locale where the radix character is not defined, + * the radix character defaults to a period (.)" + */ +static char internalDecimalPoint[MAX_LOCALE_SEPARATOR_LENGTH + 1] = "."; +static char internalThousandSeparator[MAX_LOCALE_SEPARATOR_LENGTH + 1] = ","; +static char internalGrouping[MAX_LOCALE_GROUPS] = { (char)NO_GROUPING }; + +static const char internalDigitsLower[] = "0123456789abcdefghijklmnopqrstuvwxyz"; +static const char internalDigitsUpper[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +static BOOLEAN_T internalDigitsUnconverted = TRUE; +static int internalDigitArray[128]; +#if TRIO_EXTENSION +static BOOLEAN_T internalCollationUnconverted = TRUE; +static char internalCollationArray[MAX_CHARACTER_CLASS][MAX_CHARACTER_CLASS]; +#endif + +static volatile trio_callback_t internalEnterCriticalRegion = NULL; +static volatile trio_callback_t internalLeaveCriticalRegion = NULL; +static userdef_T *internalUserDef = NULL; + + +/************************************************************************* + * trio_strerror [public] + */ +const char *trio_strerror(int errorcode) +{ + /* Textual versions of the error codes */ + switch (TRIO_ERROR_CODE(errorcode)) + { + case TRIO_EOF: + return "End of file"; + case TRIO_EINVAL: + return "Invalid argument"; + case TRIO_ETOOMANY: + return "Too many arguments"; + case TRIO_EDBLREF: + return "Double reference"; + case TRIO_EGAP: + return "Reference gap"; + case TRIO_ENOMEM: + return "Out of memory"; + case TRIO_ERANGE: + return "Invalid range"; + default: + return "Unknown"; + } +} + +/************************************************************************* + * TrioIsQualifier [private] + * + * Description: + * Remember to add all new qualifiers to this function. + * QUALIFIER_POSITION must not be added. + */ +static BOOLEAN_T +TrioIsQualifier(const char ch) +{ + /* QUALIFIER_POSITION is not included */ + switch (ch) + { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case QUALIFIER_PLUS: + case QUALIFIER_MINUS: + case QUALIFIER_SPACE: + case QUALIFIER_DOT: + case QUALIFIER_STAR: + case QUALIFIER_ALTERNATIVE: + case QUALIFIER_SHORT: + case QUALIFIER_LONG: + case QUALIFIER_LONG_UPPER: + case QUALIFIER_CIRCUMFLEX: +#if defined(QUALIFIER_SIZE_T) + case QUALIFIER_SIZE_T: +#endif +#if defined(QUALIFIER_PTRDIFF_T) + case QUALIFIER_PTRDIFF_T: +#endif +#if defined(QUALIFIER_INTMAX_T) + case QUALIFIER_INTMAX_T: +#endif +#if defined(QUALIFIER_QUAD) + case QUALIFIER_QUAD: +#endif +#if defined(QUALIFIER_SIZE_T_UPPER) + case QUALIFIER_SIZE_T_UPPER: +#endif +#if defined(QUALIFIER_WIDECHAR) + case QUALIFIER_WIDECHAR: +#endif +#if defined(QUALIFIER_QUOTE) + case QUALIFIER_QUOTE: +#endif +#if defined(QUALIFIER_STICKY) + case QUALIFIER_STICKY: +#endif +#if defined(QUALIFIER_VARSIZE) + case QUALIFIER_VARSIZE: +#endif +#if defined(QUALIFIER_PARAM) + case QUALIFIER_PARAM: +#endif +#if defined(QUALIFIER_FIXED_SIZE) + case QUALIFIER_FIXED_SIZE: +#endif +#ifdef QUALIFIER_ESCAPE + case QUALIFIER_ESCAPE: +#endif + return TRUE; + default: + return FALSE; + } +} + +/************************************************************************* + * TrioGenerateNan [private] + * + * Calculating NaN portably is difficult. Some compilers will emit + * warnings about divide by zero, and others will simply fail to + * generate a NaN. + */ +static double +TrioGenerateNaN(void) +{ +#if defined(TRIO_COMPILER_SUPPORTS_C99) + return nan(NULL); +#elif defined(DBL_QNAN) + return DBL_QNAN; +#elif defined(PLATFORM_UNIX) + double value; + void (*signal_handler)(int); + + signal_handler = signal(SIGFPE, SIG_IGN); + value = 0.0 / 0.0; + signal(SIGFPE, signal_handler); + return value; +#else + return 0.0 / 0.0; +#endif +} + +/************************************************************************* + * TrioIsNan [private] + */ +static int +TrioIsNan(double number) +{ +#ifdef isnan + /* C99 defines isnan() as a macro */ + return isnan(number); +#else + double integral, fraction; + + return (/* NaN is the only number which does not compare to itself */ + (number != number) || + /* Fallback solution if NaN compares to NaN */ + ((number != 0.0) && + (fraction = modf(number, &integral), + integral == fraction))); +#endif +} + +/************************************************************************* + * TrioIsInfinite [private] + */ +static int +TrioIsInfinite(double number) +{ +#ifdef isinf + /* C99 defines isinf() as a macro */ + return isinf(number); +#else + return ((number == HUGE_VAL) ? 1 : ((number == -HUGE_VAL) ? -1 : 0)); +#endif +} + +/************************************************************************* + * TrioSetLocale [private] + */ +#if defined(USE_LOCALE) +static void +TrioSetLocale(void) +{ + internalLocaleValues = (struct lconv *)localeconv(); + if (internalLocaleValues) + { + if ((internalLocaleValues->decimal_point) && + (internalLocaleValues->decimal_point[0] != NIL)) + { + StrCopyMax(internalDecimalPoint, + sizeof(internalDecimalPoint), + internalLocaleValues->decimal_point); + } + if ((internalLocaleValues->thousands_sep) && + (internalLocaleValues->thousands_sep[0] != NIL)) + { + StrCopyMax(internalThousandSeparator, + sizeof(internalThousandSeparator), + internalLocaleValues->thousands_sep); + } + if ((internalLocaleValues->grouping) && + (internalLocaleValues->grouping[0] != NIL)) + { + StrCopyMax(internalGrouping, + sizeof(internalGrouping), + internalLocaleValues->grouping); + } + } +} +#endif /* defined(USE_LOCALE) */ + +/************************************************************************* + * TrioGetPosition [private] + * + * Get the %n$ position. + */ +static int +TrioGetPosition(const char *format, + int *indexPointer) +{ + char *tmpformat; + int number = 0; + int index = *indexPointer; + + number = (int)StrToLong(&format[index], &tmpformat, BASE_DECIMAL); + index = (int)(tmpformat - format); + if ((number != 0) && (QUALIFIER_POSITION == format[index++])) + { + *indexPointer = index; + /* + * number is decreased by 1, because n$ starts from 1, whereas + * the array it is indexing starts from 0. + */ + return number - 1; + } + return NO_POSITION; +} + +/************************************************************************* + * TrioFindNamespace [private] + * + * Find registered user-defined specifier. + * The prev argument is used for optimisation only. + */ +static userdef_T * +TrioFindNamespace(const char *name, userdef_T **prev) +{ + userdef_T *def; + + if (internalEnterCriticalRegion) + (void)internalEnterCriticalRegion(NULL); + + for (def = internalUserDef; def; def = def->next) + { + /* Case-sensitive string comparison */ + if (StrEqualCase(def->name, name)) + break; + + if (prev) + *prev = def; + } + + if (internalLeaveCriticalRegion) + (void)internalLeaveCriticalRegion(NULL); + + return def; +} + +/************************************************************************* + * TrioPreprocess [private] + * + * Description: + * Parse the format string + */ +static int +TrioPreprocess(int type, + const char *format, + parameter_T *parameters, + va_list arglist, + void **argarray) +{ +#if TRIO_ERRORS + /* Count the number of times a parameter is referenced */ + unsigned short usedEntries[MAX_PARAMETERS]; +#endif + /* Parameter counters */ + int parameterPosition; + int currentParam; + int maxParam = -1; + /* Utility variables */ + unsigned long flags; + int width; + int precision; + int varsize; +#ifdef QUALIFIER_ESCAPE + enum dg_escape escape; +#endif + int base; + int index; /* Index into formatting string */ + int dots; /* Count number of dots in modifier part */ + BOOLEAN_T positional; /* Does the specifier have a positional? */ + BOOLEAN_T got_sticky = FALSE; /* Are there any sticky modifiers at all? */ + /* + * indices specifies the order in which the parameters must be + * read from the va_args (this is necessary to handle positionals) + */ + int indices[MAX_PARAMETERS]; + int pos = 0; + /* Various variables */ + char ch; +#if defined(TRIO_COMPILER_SUPPORTS_MULTIBYTE) + int charlen; +#endif + int i = -1; + int num; + char *tmpformat; + + +#if TRIO_ERRORS + /* + * The 'parameters' array is not initialized, but we need to + * know which entries we have used. + */ + memset(usedEntries, 0, sizeof(usedEntries)); +#endif + + index = 0; + parameterPosition = 0; +#if defined(TRIO_COMPILER_SUPPORTS_MULTIBYTE) + mblen(NULL, 0); +#endif + + while (format[index]) + { +#if defined(TRIO_COMPILER_SUPPORTS_MULTIBYTE) + if (! isascii(format[index])) + { + /* + * Multibyte characters cannot be legal specifiers or + * modifiers, so we skip over them. + */ + charlen = mblen(&format[index], MB_LEN_MAX); + index += (charlen > 0) ? charlen : 1; + continue; /* while */ + } +#endif /* TRIO_COMPILER_SUPPORTS_MULTIBYTE */ + if (CHAR_IDENTIFIER == format[index++]) + { + if (CHAR_IDENTIFIER == format[index]) + { + index++; + continue; /* while */ + } + + flags = FLAGS_NEW; + dots = 0; + currentParam = TrioGetPosition(format, &index); + positional = (NO_POSITION != currentParam); + if (!positional) + { + /* We have no positional, get the next counter */ + currentParam = parameterPosition; + } + if(currentParam >= MAX_PARAMETERS) + { + /* Bail out completely to make the error more obvious */ + return TRIO_ERROR_RETURN(TRIO_ETOOMANY, index); + } + + if (currentParam > maxParam) + maxParam = currentParam; + + /* Default values */ + width = NO_WIDTH; + precision = NO_PRECISION; + base = NO_BASE; + varsize = NO_SIZE; +#ifdef QUALIFIER_ESCAPE + escape = ESCAPE_NONE; +#endif + + while (TrioIsQualifier(format[index])) + { + ch = format[index++]; + + switch (ch) + { + case QUALIFIER_SPACE: + flags |= FLAGS_SPACE; + break; + + case QUALIFIER_PLUS: + flags |= FLAGS_SHOWSIGN; + break; + + case QUALIFIER_MINUS: + flags |= FLAGS_LEFTADJUST; + flags &= ~FLAGS_NILPADDING; + break; + + case QUALIFIER_ALTERNATIVE: + flags |= FLAGS_ALTERNATIVE; + break; + + case QUALIFIER_DOT: + if (dots == 0) /* Precision */ + { + dots++; + + /* Skip if no precision */ + if (QUALIFIER_DOT == format[index]) + break; + + /* After the first dot we have the precision */ + flags |= FLAGS_PRECISION; + if ((QUALIFIER_STAR == format[index]) || + (QUALIFIER_PARAM == format[index])) + { + index++; + flags |= FLAGS_PRECISION_PARAMETER; + + precision = TrioGetPosition(format, &index); + if (precision == NO_POSITION) + { + parameterPosition++; + if (positional) + precision = parameterPosition; + else + { + precision = currentParam; + currentParam = precision + 1; + } + } + else + { + if (! positional) + currentParam = precision + 1; + if (width > maxParam) + maxParam = precision; + } + if (currentParam > maxParam) + maxParam = currentParam; + } + else + { + precision = StrToLong(&format[index], &tmpformat, BASE_DECIMAL); + index = (int)(tmpformat - format); + } + } + else if (dots == 1) /* Base */ + { + dots++; + + /* After the second dot we have the base */ + flags |= FLAGS_BASE; + if ((QUALIFIER_STAR == format[index]) || + (QUALIFIER_PARAM == format[index])) + { + index++; + flags |= FLAGS_BASE_PARAMETER; + base = TrioGetPosition(format, &index); + if (base == NO_POSITION) + { + parameterPosition++; + if (positional) + base = parameterPosition; + else + { + base = currentParam; + currentParam = base + 1; + } + } + else + { + if (! positional) + currentParam = base + 1; + if (base > maxParam) + maxParam = base; + } + if (currentParam > maxParam) + maxParam = currentParam; + } + else + { + base = StrToLong(&format[index], &tmpformat, BASE_DECIMAL); + if (base > MAX_BASE) + return TRIO_ERROR_RETURN(TRIO_EINVAL, index); + index = (int)(tmpformat - format); + } + } + else + { + return TRIO_ERROR_RETURN(TRIO_EINVAL, index); + } + break; /* QUALIFIER_DOT */ + + case QUALIFIER_PARAM: + type = TYPE_PRINT; + /* FALLTHROUGH */ + case QUALIFIER_STAR: + /* This has different meanings for print and scan */ + if (TYPE_PRINT == type) + { + /* Read with from parameter */ + flags |= (FLAGS_WIDTH | FLAGS_WIDTH_PARAMETER); + width = TrioGetPosition(format, &index); + if (width == NO_POSITION) + { + parameterPosition++; + if (positional) + width = parameterPosition; + else + { + width = currentParam; + currentParam = width + 1; + } + } + else + { + if (! positional) + currentParam = width + 1; + if (width > maxParam) + maxParam = width; + } + if (currentParam > maxParam) + maxParam = currentParam; + } + else + { + /* Scan, but do not store result */ + flags |= FLAGS_IGNORE; + } + + break; /* QUALIFIER_STAR */ + + case '0': + if (! (flags & FLAGS_LEFTADJUST)) + flags |= FLAGS_NILPADDING; + /* FALLTHROUGH */ + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + flags |= FLAGS_WIDTH; + /* &format[index - 1] is used to "rewind" the read + * character from format + */ + width = StrToLong(&format[index - 1], &tmpformat, BASE_DECIMAL); + index = (int)(tmpformat - format); + break; + + case QUALIFIER_SHORT: + if (flags & FLAGS_SHORTSHORT) + return TRIO_ERROR_RETURN(TRIO_EINVAL, index); + else if (flags & FLAGS_SHORT) + flags |= FLAGS_SHORTSHORT; + else + flags |= FLAGS_SHORT; + break; + + case QUALIFIER_LONG: + if (flags & FLAGS_QUAD) + return TRIO_ERROR_RETURN(TRIO_EINVAL, index); + else if (flags & FLAGS_LONG) + flags |= FLAGS_QUAD; + else + flags |= FLAGS_LONG; + break; + + case QUALIFIER_LONG_UPPER: + flags |= FLAGS_LONGDOUBLE; + break; + +#if defined(QUALIFIER_SIZE_T) + case QUALIFIER_SIZE_T: + flags |= FLAGS_SIZE_T; + /* Modify flags for later truncation of number */ + if (sizeof(size_t) == sizeof(trio_ulonglong_t)) + flags |= FLAGS_QUAD; + else if (sizeof(size_t) == sizeof(long)) + flags |= FLAGS_LONG; + break; +#endif + +#if defined(QUALIFIER_PTRDIFF_T) + case QUALIFIER_PTRDIFF_T: + flags |= FLAGS_PTRDIFF_T; + if (sizeof(ptrdiff_t) == sizeof(trio_ulonglong_t)) + flags |= FLAGS_QUAD; + else if (sizeof(ptrdiff_t) == sizeof(long)) + flags |= FLAGS_LONG; + break; +#endif + +#if defined(QUALIFIER_INTMAX_T) + case QUALIFIER_INTMAX_T: + flags |= FLAGS_INTMAX_T; + if (sizeof(trio_intmax_t) == sizeof(trio_ulonglong_t)) + flags |= FLAGS_QUAD; + else if (sizeof(trio_intmax_t) == sizeof(long)) + flags |= FLAGS_LONG; + break; +#endif + +#if defined(QUALIFIER_QUAD) + case QUALIFIER_QUAD: + flags |= FLAGS_QUAD; + break; +#endif + +#if defined(QUALIFIER_FIXED_SIZE) + case QUALIFIER_FIXED_SIZE: + if (flags & FLAGS_FIXED_SIZE) + return TRIO_ERROR_RETURN(TRIO_EINVAL, index); + + if (flags & (FLAGS_ALL_SIZES | FLAGS_LONGDOUBLE | + FLAGS_WIDECHAR | FLAGS_VARSIZE_PARAMETER)) + return TRIO_ERROR_RETURN(TRIO_EINVAL, index); + + if ((format[index] == '6') && + (format[index + 1] == '4')) + { + varsize = sizeof(trio_int64_t); + index += 2; + } + else if ((format[index] == '3') && + (format[index + 1] == '2')) + { + varsize = sizeof(trio_int32_t); + index += 2; + } + else if ((format[index] == '1') && + (format[index + 1] == '6')) + { + varsize = sizeof(trio_int16_t); + index += 2; + } + else if (format[index] == '8') + { + varsize = sizeof(trio_int8_t); + index++; + } + else + return TRIO_ERROR_RETURN(TRIO_EINVAL, index); + + flags |= FLAGS_FIXED_SIZE; + break; +#endif + +#ifdef QUALIFIER_ESCAPE + case QUALIFIER_ESCAPE: + switch (format[index++]) { + case 'U': escape = ESCAPE_ULM; break; + case 'X': escape = ESCAPE_XML; break; + case 'S': escape = ESCAPE_SQL; break; + default: return TRIO_ERROR_RETURN(TRIO_EINVAL,index); + } + break; +#endif + + +#if defined(QUALIFIER_WIDECHAR) + case QUALIFIER_WIDECHAR: + flags |= FLAGS_WIDECHAR; + break; +#endif + +#if defined(QUALIFIER_SIZE_T_UPPER) + case QUALIFIER_SIZE_T_UPPER: + break; +#endif + +#if defined(QUALIFIER_QUOTE) + case QUALIFIER_QUOTE: + flags |= FLAGS_QUOTE; + break; +#endif + +#if defined(QUALIFIER_STICKY) + case QUALIFIER_STICKY: + flags |= FLAGS_STICKY; + got_sticky = TRUE; + break; +#endif + +#if defined(QUALIFIER_VARSIZE) + case QUALIFIER_VARSIZE: + flags |= FLAGS_VARSIZE_PARAMETER; + parameterPosition++; + if (positional) + varsize = parameterPosition; + else + { + varsize = currentParam; + currentParam = varsize + 1; + } + if (currentParam > maxParam) + maxParam = currentParam; + break; +#endif + + default: + /* Bail out completely to make the error more obvious */ + return TRIO_ERROR_RETURN(TRIO_EINVAL, index); + } + } /* while qualifier */ + + /* + * Parameters only need the type and value. The value is + * read later. + */ + if (flags & FLAGS_WIDTH_PARAMETER) + { +#if TRIO_ERRORS + usedEntries[width] += 1; +#endif + parameters[pos].type = FORMAT_PARAMETER; + indices[width] = pos; + width = pos++; + } + if (flags & FLAGS_PRECISION_PARAMETER) + { +#if TRIO_ERRORS + usedEntries[precision] += 1; +#endif + parameters[pos].type = FORMAT_PARAMETER; + indices[precision] = pos; + precision = pos++; + } + if (flags & FLAGS_BASE_PARAMETER) + { +#if TRIO_ERRORS + usedEntries[base] += 1; +#endif + parameters[pos].type = FORMAT_PARAMETER; + indices[base] = pos; + base = pos++; + } + if (flags & FLAGS_VARSIZE_PARAMETER) + { +#if TRIO_ERRORS + usedEntries[varsize] += 1; +#endif + parameters[pos].type = FORMAT_PARAMETER; + indices[varsize] = pos; + varsize = pos++; + } + + indices[currentParam] = pos; + + switch (format[index++]) + { +#if defined(SPECIFIER_CHAR_UPPER) + case SPECIFIER_CHAR_UPPER: + flags |= FLAGS_WIDECHAR; + /* FALLTHROUGH */ +#endif + case SPECIFIER_CHAR: + if (flags & FLAGS_LONG) + flags |= FLAGS_WIDECHAR; + else if (flags & FLAGS_SHORT) + flags &= ~FLAGS_WIDECHAR; + parameters[pos].type = FORMAT_CHAR; + break; + +#if defined(SPECIFIER_STRING_UPPER) + case SPECIFIER_STRING_UPPER: + flags |= FLAGS_WIDECHAR; + /* FALLTHROUGH */ +#endif + case SPECIFIER_STRING: + if (flags & FLAGS_LONG) + flags |= FLAGS_WIDECHAR; + else if (flags & FLAGS_SHORT) + flags &= ~FLAGS_WIDECHAR; + parameters[pos].type = FORMAT_STRING; + break; + + + case SPECIFIER_GROUP: + if (TYPE_SCAN == type) + { + int depth = 1; + parameters[pos].type = FORMAT_GROUP; + if (format[index] == QUALIFIER_CIRCUMFLEX) + index++; + if (format[index] == SPECIFIER_UNGROUP) + index++; + if (format[index] == QUALIFIER_MINUS) + index++; + /* Skip nested brackets */ + while (format[index] != NIL) + { + if (format[index] == SPECIFIER_GROUP) + { + depth++; + } + else if (format[index] == SPECIFIER_UNGROUP) + { + if (--depth <= 0) + { + index++; + break; + } + } + index++; + } + } + break; + + case SPECIFIER_INTEGER: + parameters[pos].type = FORMAT_INT; + break; + + case SPECIFIER_UNSIGNED: + flags |= FLAGS_UNSIGNED; + parameters[pos].type = FORMAT_INT; + break; + + case SPECIFIER_DECIMAL: + /* Disable base modifier */ + flags &= ~FLAGS_BASE_PARAMETER; + base = BASE_DECIMAL; + parameters[pos].type = FORMAT_INT; + break; + + case SPECIFIER_OCTAL: + flags &= ~FLAGS_BASE_PARAMETER; + base = BASE_OCTAL; + parameters[pos].type = FORMAT_INT; + break; + +#if defined(SPECIFIER_BINARY) + case SPECIFIER_BINARY_UPPER: + flags |= FLAGS_UPPER; + /* FALLTHROUGH */ + case SPECIFIER_BINARY: + flags |= FLAGS_NILPADDING; + flags &= ~FLAGS_BASE_PARAMETER; + base = BASE_BINARY; + parameters[pos].type = FORMAT_INT; + break; +#endif + + case SPECIFIER_HEX_UPPER: + flags |= FLAGS_UPPER; + /* FALLTHROUGH */ + case SPECIFIER_HEX: + flags |= FLAGS_UNSIGNED; + flags &= ~FLAGS_BASE_PARAMETER; + base = BASE_HEX; + parameters[pos].type = FORMAT_INT; + break; + + case SPECIFIER_FLOAT_E_UPPER: + flags |= FLAGS_UPPER; + /* FALLTHROUGH */ + case SPECIFIER_FLOAT_E: + flags |= FLAGS_FLOAT_E; + parameters[pos].type = FORMAT_DOUBLE; + break; + + case SPECIFIER_FLOAT_G_UPPER: + flags |= FLAGS_UPPER; + /* FALLTHROUGH */ + case SPECIFIER_FLOAT_G: + flags |= FLAGS_FLOAT_G; + parameters[pos].type = FORMAT_DOUBLE; + break; + + case SPECIFIER_FLOAT_F_UPPER: + flags |= FLAGS_UPPER; + /* FALLTHROUGH */ + case SPECIFIER_FLOAT_F: + parameters[pos].type = FORMAT_DOUBLE; + break; + + case SPECIFIER_POINTER: + parameters[pos].type = FORMAT_POINTER; + break; + + case SPECIFIER_COUNT: + parameters[pos].type = FORMAT_COUNT; + break; + +#if defined(SPECIFIER_HEXFLOAT) +# if defined(SPECIFIER_HEXFLOAT_UPPER) + case SPECIFIER_HEXFLOAT_UPPER: + flags |= FLAGS_UPPER; + /* FALLTHROUGH */ +# endif + case SPECIFIER_HEXFLOAT: + base = BASE_HEX; + parameters[pos].type = FORMAT_DOUBLE; + break; +#endif + +#if defined(FORMAT_ERRNO) + case SPECIFIER_ERRNO: + parameters[pos].type = FORMAT_ERRNO; + break; +#endif + +#if defined(SPECIFIER_USER_DEFINED_BEGIN) + case SPECIFIER_USER_DEFINED_BEGIN: + { + unsigned int max; + int without_namespace = TRUE; + + parameters[pos].type = FORMAT_USER_DEFINED; + parameters[pos].user_name[0] = NIL; + tmpformat = (char *)&format[index]; + + while ((ch = format[index])) + { + index++; + if (ch == SPECIFIER_USER_DEFINED_END) + { + if (without_namespace) + { + /* We must get the handle first */ + parameters[pos].type = FORMAT_PARAMETER; + parameters[pos].indexAfterSpecifier = index; + parameters[pos].flags = FLAGS_USER_DEFINED; + /* Adjust parameters for insertion of new one */ + pos++; +# if TRIO_ERRORS + usedEntries[currentParam] += 1; +# endif + parameters[pos].type = FORMAT_USER_DEFINED; + currentParam++; + indices[currentParam] = pos; + if (currentParam > maxParam) + maxParam = currentParam; + } + /* Copy the user data */ + max = (unsigned int)(&format[index] - tmpformat); + if (max > MAX_USER_DATA) + max = MAX_USER_DATA; + StrCopyMax(parameters[pos].user_data, + max, + tmpformat); + break; /* while */ + } + if (ch == SPECIFIER_USER_DEFINED_SEPARATOR) + { + without_namespace = FALSE; + /* Copy the namespace for later looking-up */ + max = (int)(&format[index] - tmpformat); + if (max > MAX_USER_NAME) + max = MAX_USER_NAME; + StrCopyMax(parameters[pos].user_name, + max, + tmpformat); + tmpformat = (char *)&format[index]; + } + } + if (ch != SPECIFIER_USER_DEFINED_END) + return TRIO_ERROR_RETURN(TRIO_EINVAL, index); + } + break; +#endif /* defined(SPECIFIER_USER_DEFINED_BEGIN) */ + + default: + /* Bail out completely to make the error more obvious */ + return TRIO_ERROR_RETURN(TRIO_EINVAL, index); + } + +#if TRIO_ERRORS + /* Count the number of times this entry has been used */ + usedEntries[currentParam] += 1; +#endif + + /* Find last sticky parameters */ + if (got_sticky && !(flags & FLAGS_STICKY)) + { + for (i = pos - 1; i >= 0; i--) + { + if (parameters[i].type == FORMAT_PARAMETER) + continue; + if ((parameters[i].flags & FLAGS_STICKY) && + (parameters[i].type == parameters[pos].type)) + { + /* Do not overwrite current qualifiers */ + flags |= (parameters[i].flags & (unsigned long)~FLAGS_STICKY); + if (width == NO_WIDTH) + width = parameters[i].width; + if (precision == NO_PRECISION) + precision = parameters[i].precision; + if (base == NO_BASE) + base = parameters[i].base; + break; + } + } + } + + parameters[pos].indexAfterSpecifier = index; + parameters[pos].flags = flags; + parameters[pos].width = width; + parameters[pos].precision = precision; + parameters[pos].base = (base == NO_BASE) ? BASE_DECIMAL : base; + parameters[pos].varsize = varsize; +#ifdef QUALIFIER_ESCAPE + parameters[pos].escape = escape; +#endif + pos++; + + if (! positional) + parameterPosition++; + + } /* if identifier */ + + } /* while format characters left */ + + for (num = 0; num <= maxParam; num++) + { +#if TRIO_ERRORS + if (usedEntries[num] != 1) + { + if (usedEntries[num] == 0) /* gap detected */ + return TRIO_ERROR_RETURN(TRIO_EGAP, num); + else /* double references detected */ + return TRIO_ERROR_RETURN(TRIO_EDBLREF, num); + } +#endif + + i = indices[num]; + + /* + * FORMAT_PARAMETERS are only present if they must be read, + * so it makes no sense to check the ignore flag (besides, + * the flags variable is not set for that particular type) + */ + if ((parameters[i].type != FORMAT_PARAMETER) && + (parameters[i].flags & FLAGS_IGNORE)) + continue; /* for all arguments */ + + /* + * The stack arguments are read according to ANSI C89 + * default argument promotions: + * + * char = int + * short = int + * unsigned char = unsigned int + * unsigned short = unsigned int + * float = double + * + * In addition to the ANSI C89 these types are read (the + * default argument promotions of C99 has not been + * considered yet) + * + * long long + * long double + * size_t + * ptrdiff_t + * intmax_t + */ + switch (parameters[i].type) + { + case FORMAT_GROUP: + case FORMAT_STRING: +#if TRIO_WIDECHAR + if (flags & FLAGS_WIDECHAR) + { + parameters[i].data.wstring = (argarray == NULL) + ? va_arg(arglist, wchar_t *) + : (wchar_t *)(argarray[num]); + } + else +#endif + { + parameters[i].data.string = (argarray == NULL) + ? va_arg(arglist, char *) + : (char *)(argarray[num]); + } + break; + + case FORMAT_POINTER: + case FORMAT_COUNT: + case FORMAT_USER_DEFINED: + case FORMAT_UNKNOWN: + parameters[i].data.pointer = (argarray == NULL) + ? va_arg(arglist, void *) + : argarray[num]; + break; + + case FORMAT_CHAR: + case FORMAT_INT: + if (TYPE_SCAN == type) + { + if (argarray == NULL) + parameters[i].data.pointer = + (trio_uintmax_t *)va_arg(arglist, void *); + else + { + if (parameters[i].type == FORMAT_CHAR) + parameters[i].data.pointer = + (trio_uintmax_t *)((char *)argarray[num]); + else if (parameters[i].flags & FLAGS_SHORT) + parameters[i].data.pointer = + (trio_uintmax_t *)((short *)argarray[num]); + else + parameters[i].data.pointer = + (trio_uintmax_t *)((int *)argarray[num]); + } + } + else + { +#if defined(QUALIFIER_VARSIZE) || defined(QUALIFIER_FIXED_SIZE) + if ((parameters[i].flags & FLAGS_VARSIZE_PARAMETER) || + (parameters[i].flags & FLAGS_FIXED_SIZE)) + { + if (parameters[i].flags & FLAGS_VARSIZE_PARAMETER) + { + /* + * Variable sizes are mapped onto the fixed sizes, in + * accordance with integer promotion. + * + * Please note that this may not be portable, as we + * only guess the size, not the layout of the numbers. + * For example, if int is little-endian, and long is + * big-endian, then this will fail. + */ + varsize = (int)parameters[parameters[i].varsize].data.number.as_unsigned; + } + else + { + /* Used for the I modifiers */ + varsize = parameters[i].varsize; + } + parameters[i].flags &= ~FLAGS_ALL_VARSIZES; + + if (varsize <= (int)sizeof(int)) + ; + else if (varsize <= (int)sizeof(long)) + parameters[i].flags |= FLAGS_LONG; +#if defined(QUALIFIER_INTMAX_T) + else if (varsize <= (int)sizeof(trio_longlong_t)) + parameters[i].flags |= FLAGS_QUAD; + else + parameters[i].flags |= FLAGS_INTMAX_T; +#else + else + parameters[i].flags |= FLAGS_QUAD; +#endif + } +#endif /* defined(QUALIFIER_VARSIZE) */ +#if defined(QUALIFIER_SIZE_T) || defined(QUALIFIER_SIZE_T_UPPER) + if (parameters[i].flags & FLAGS_SIZE_T) + parameters[i].data.number.as_unsigned = (argarray == NULL) + ? (trio_uintmax_t)va_arg(arglist, size_t) + : (trio_uintmax_t)(*((size_t *)argarray[num])); + else +#endif +#if defined(QUALIFIER_PTRDIFF_T) + if (parameters[i].flags & FLAGS_PTRDIFF_T) + parameters[i].data.number.as_unsigned = (argarray == NULL) + ? (trio_uintmax_t)va_arg(arglist, ptrdiff_t) + : (trio_uintmax_t)(*((ptrdiff_t *)argarray[num])); + else +#endif +#if defined(QUALIFIER_INTMAX_T) + if (parameters[i].flags & FLAGS_INTMAX_T) + parameters[i].data.number.as_unsigned = (argarray == NULL) + ? (trio_uintmax_t)va_arg(arglist, trio_intmax_t) + : (trio_uintmax_t)(*((trio_intmax_t *)argarray[num])); + else +#endif + if (parameters[i].flags & FLAGS_QUAD) + parameters[i].data.number.as_unsigned = (argarray == NULL) + ? (trio_uintmax_t)va_arg(arglist, trio_ulonglong_t) + : (trio_uintmax_t)(*((trio_ulonglong_t *)argarray[num])); + else if (parameters[i].flags & FLAGS_LONG) + parameters[i].data.number.as_unsigned = (argarray == NULL) + ? (trio_uintmax_t)va_arg(arglist, long) + : (trio_uintmax_t)(*((long *)argarray[num])); + else + { + if (argarray == NULL) + parameters[i].data.number.as_unsigned = (trio_uintmax_t)va_arg(arglist, int); + else + { + if (parameters[i].type == FORMAT_CHAR) + parameters[i].data.number.as_unsigned = (trio_uintmax_t)(*((char *)argarray[num])); + else if (parameters[i].flags & FLAGS_SHORT) + parameters[i].data.number.as_unsigned = (trio_uintmax_t)(*((short *)argarray[num])); + else + parameters[i].data.number.as_unsigned = (trio_uintmax_t)(*((int *)argarray[num])); + } + } + } + break; + + case FORMAT_PARAMETER: + /* + * The parameter for the user-defined specifier is a pointer, + * whereas the rest (width, precision, base) uses an integer. + */ + if (parameters[i].flags & FLAGS_USER_DEFINED) + parameters[i].data.pointer = (argarray == NULL) + ? va_arg(arglist, void *) + : argarray[num]; + else + parameters[i].data.number.as_unsigned = (argarray == NULL) + ? (trio_uintmax_t)va_arg(arglist, int) + : (trio_uintmax_t)(*((int *)argarray[num])); + break; + + case FORMAT_DOUBLE: + if (TYPE_SCAN == type) + { + if (parameters[i].flags & FLAGS_LONG) + parameters[i].data.longdoublePointer = (argarray == NULL) + ? va_arg(arglist, long double *) + : (long double *)((long double *)argarray[num]); + else + { + if (argarray == NULL) + parameters[i].data.doublePointer = + va_arg(arglist, double *); + else + { + if (parameters[i].flags & FLAGS_SHORT) + parameters[i].data.doublePointer = + (double *)((float *)argarray[num]); + else + parameters[i].data.doublePointer = + (double *)((double *)argarray[num]); + } + } + } + else + { + if (parameters[i].flags & FLAGS_LONG) + parameters[i].data.longdoubleNumber = (argarray == NULL) + ? va_arg(arglist, long double) + : (long double)(*((long double *)argarray[num])); + else + { + if (argarray == NULL) + parameters[i].data.longdoubleNumber = (long double)va_arg(arglist, double); + else + { + if (parameters[i].flags & FLAGS_SHORT) + parameters[i].data.longdoubleNumber = (long double)(*((float *)argarray[num])); + else + parameters[i].data.longdoubleNumber = (long double)(long double)(*((double *)argarray[num])); + } + } + } + break; + +#if defined(FORMAT_ERRNO) + case FORMAT_ERRNO: + parameters[i].data.errorNumber = errno; + break; +#endif + + default: + break; + } + } /* for all specifiers */ + return num; +} + + +/************************************************************************* + * + * @FORMATTING + * + ************************************************************************/ + + +/************************************************************************* + * TrioWriteNumber [private] + * + * Description: + * Output a number. + * The complexity of this function is a result of the complexity + * of the dependencies of the flags. + */ +static void +TrioWriteNumber(trio_T *self, + trio_uintmax_t number, + unsigned long flags, + int width, + int precision, + int base) +{ + BOOLEAN_T isNegative; + char buffer[MAX_CHARS_IN(trio_uintmax_t) * (1 + MAX_LOCALE_SEPARATOR_LENGTH) + 1]; + char *bufferend; + char *pointer; + const char *digits; + int i; + int length; + char *p; + int charsPerThousand; + int groupingIndex; + int count; + + assert(VALID(self)); + assert(VALID(self->OutStream)); + assert((base >= MIN_BASE && base <= MAX_BASE) || (base == NO_BASE)); + + digits = (flags & FLAGS_UPPER) ? internalDigitsUpper : internalDigitsLower; + + isNegative = (flags & FLAGS_UNSIGNED) + ? FALSE + : ((trio_intmax_t)number < 0); + if (isNegative) + number = -number; + + if (flags & FLAGS_QUAD) + number &= (trio_ulonglong_t)-1; + else if (flags & FLAGS_LONG) + number &= (unsigned long)-1; + else + number &= (unsigned int)-1; + + /* Build number */ + pointer = bufferend = &buffer[sizeof(buffer) - 1]; + *pointer-- = NIL; + charsPerThousand = (int)internalGrouping[0]; + groupingIndex = 1; + for (i = 1; i < (int)sizeof(buffer); i++) + { + *pointer-- = digits[number % base]; + number /= base; + if (number == 0) + break; + + if ((flags & FLAGS_QUOTE) + && (charsPerThousand != NO_GROUPING) + && (i % charsPerThousand == 0)) + { + /* + * We are building the number from the least significant + * to the most significant digit, so we have to copy the + * thousand separator backwards + */ + length = StrLength(internalThousandSeparator); + if (((int)(pointer - buffer) - length) > 0) + { + p = &internalThousandSeparator[length - 1]; + while (length-- > 0) + *pointer-- = *p--; + } + + /* Advance to next grouping number */ + switch (internalGrouping[groupingIndex]) + { + case CHAR_MAX: /* Disable grouping */ + charsPerThousand = NO_GROUPING; + break; + case 0: /* Repeat last group */ + break; + default: + charsPerThousand = (int)internalGrouping[groupingIndex++]; + break; + } + } + } + + /* Adjust width */ + width -= (bufferend - pointer) - 1; + + /* Adjust precision */ + if (NO_PRECISION != precision) + { + precision -= (bufferend - pointer) - 1; + if (precision < 0) + precision = 0; + flags |= FLAGS_NILPADDING; + } + + /* Adjust width further */ + if (isNegative || (flags & FLAGS_SHOWSIGN) || (flags & FLAGS_SPACE)) + width--; + if (flags & FLAGS_ALTERNATIVE) + { + switch (base) + { + case BASE_BINARY: + case BASE_HEX: + width -= 2; + break; + case BASE_OCTAL: + width--; + break; + default: + break; + } + } + + /* Output prefixes spaces if needed */ + if (! ((flags & FLAGS_LEFTADJUST) || + ((flags & FLAGS_NILPADDING) && (precision == NO_PRECISION)))) + { + count = (precision == NO_PRECISION) ? 0 : precision; + while (width-- > count) + self->OutStream(self, CHAR_ADJUST); + } + + /* width has been adjusted for signs and alternatives */ + if (isNegative) + self->OutStream(self, '-'); + else if (flags & FLAGS_SHOWSIGN) + self->OutStream(self, '+'); + else if (flags & FLAGS_SPACE) + self->OutStream(self, ' '); + + if (flags & FLAGS_ALTERNATIVE) + { + switch (base) + { + case BASE_BINARY: + self->OutStream(self, '0'); + self->OutStream(self, (flags & FLAGS_UPPER) ? 'B' : 'b'); + break; + + case BASE_OCTAL: + self->OutStream(self, '0'); + break; + + case BASE_HEX: + self->OutStream(self, '0'); + self->OutStream(self, (flags & FLAGS_UPPER) ? 'X' : 'x'); + break; + + default: + break; + } /* switch base */ + } + + /* Output prefixed zero padding if needed */ + if (flags & FLAGS_NILPADDING) + { + if (precision == NO_PRECISION) + precision = width; + while (precision-- > 0) + { + self->OutStream(self, '0'); + width--; + } + } + + /* Output the number itself */ + while (*(++pointer)) + { + self->OutStream(self, *pointer); + } + + /* Output trailing spaces if needed */ + if (flags & FLAGS_LEFTADJUST) + { + while (width-- > 0) + self->OutStream(self, CHAR_ADJUST); + } +} + +/************************************************************************* + * TrioWriteStringCharacter [private] + * + * Description: + * Output a single character of a string + */ +static void +TrioWriteStringCharacter(trio_T *self, + int ch, + unsigned long flags) +{ + if (flags & FLAGS_ALTERNATIVE) + { + if (! (isprint(ch) || isspace(ch))) + { + /* + * Non-printable characters are converted to C escapes or + * \number, if no C escape exists. + */ + self->OutStream(self, CHAR_BACKSLASH); + switch (ch) + { + case '\007': self->OutStream(self, 'a'); break; + case '\b': self->OutStream(self, 'b'); break; + case '\f': self->OutStream(self, 'f'); break; + case '\n': self->OutStream(self, 'n'); break; + case '\r': self->OutStream(self, 'r'); break; + case '\t': self->OutStream(self, 't'); break; + case '\v': self->OutStream(self, 'v'); break; + case '\\': self->OutStream(self, '\\'); break; + default: + self->OutStream(self, 'x'); + TrioWriteNumber(self, (trio_intmax_t)ch, + FLAGS_UNSIGNED | FLAGS_NILPADDING, + 2, 2, BASE_HEX); + break; + } + } + else if (ch == CHAR_BACKSLASH) + { + self->OutStream(self, CHAR_BACKSLASH); + self->OutStream(self, CHAR_BACKSLASH); + } + else + { + self->OutStream(self, ch); + } + } + else + { + self->OutStream(self, ch); + } +} + +/************************************************************************* + * TrioWriteString [private] + * + * Description: + * Output a string + */ +static void +TrioWriteString(trio_T *self, + const char *string, + unsigned long flags, + int width, + int precision) +{ + int length; + int ch; + + assert(VALID(self)); + assert(VALID(self->OutStream)); + + if (string == NULL) + { + string = null; + length = sizeof(null) - 1; + /* Disable quoting for the null pointer */ + flags &= (~FLAGS_QUOTE); + width = 0; + } + else + { + length = StrLength(string); + } + if ((NO_PRECISION != precision) && + (precision < length)) + { + length = precision; + } + width -= length; + + if (flags & FLAGS_QUOTE) + self->OutStream(self, CHAR_QUOTE); + + if (! (flags & FLAGS_LEFTADJUST)) + { + while (width-- > 0) + self->OutStream(self, CHAR_ADJUST); + } + + while (length-- > 0) + { + /* The ctype parameters must be an unsigned char (or EOF) */ + ch = (int)((unsigned char)(*string++)); + TrioWriteStringCharacter(self, ch, flags); + } + + if (flags & FLAGS_LEFTADJUST) + { + while (width-- > 0) + self->OutStream(self, CHAR_ADJUST); + } + if (flags & FLAGS_QUOTE) + self->OutStream(self, CHAR_QUOTE); +} + +/************************************************************************* + * TrioWriteWideStringCharacter [private] + * + * Description: + * Output a wide string as a multi-byte sequence + */ +#if TRIO_WIDECHAR +static int +TrioWriteWideStringCharacter(trio_T *self, + wchar_t wch, + unsigned long flags, + int width) +{ + int size; + int i; + int ch; + char *string; + char buffer[MB_LEN_MAX + 1]; + + if (width == NO_WIDTH) + width = sizeof(buffer); + + size = wctomb(buffer, wch); + if ((size <= 0) || (size > width) || (buffer[0] == NIL)) + return 0; + + string = buffer; + i = size; + while ((width >= i) && (width-- > 0) && (i-- > 0)) + { + /* The ctype parameters must be an unsigned char (or EOF) */ + ch = (int)((unsigned char)(*string++)); + TrioWriteStringCharacter(self, ch, flags); + } + return size; +} +#endif /* TRIO_WIDECHAR */ + +/************************************************************************* + * TrioWriteString [private] + * + * Description: + * Output a wide character string as a multi-byte string + */ +#if TRIO_WIDECHAR +static void +TrioWriteWideString(trio_T *self, + const wchar_t *wstring, + unsigned long flags, + int width, + int precision) +{ + int length; + int size; + + assert(VALID(self)); + assert(VALID(self->OutStream)); + +#if defined(TRIO_COMPILER_SUPPORTS_MULTIBYTE) + mblen(NULL, 0); +#endif + + if (wstring == NULL) + { + TrioWriteString(self, NULL, flags, width, precision); + return; + } + + if (NO_PRECISION == precision) + { + length = INT_MAX; + } + else + { + length = precision; + width -= length; + } + + if (flags & FLAGS_QUOTE) + self->OutStream(self, CHAR_QUOTE); + + if (! (flags & FLAGS_LEFTADJUST)) + { + while (width-- > 0) + self->OutStream(self, CHAR_ADJUST); + } + + while (length > 0) + { + size = TrioWriteWideStringCharacter(self, *wstring++, flags, length); + if (size == 0) + break; /* while */ + length -= size; + } + + if (flags & FLAGS_LEFTADJUST) + { + while (width-- > 0) + self->OutStream(self, CHAR_ADJUST); + } + if (flags & FLAGS_QUOTE) + self->OutStream(self, CHAR_QUOTE); +} +#endif /* TRIO_WIDECHAR */ + +/************************************************************************* + * TrioWriteDouble [private] + */ +static void +TrioWriteDouble(trio_T *self, + long double longdoubleNumber, + unsigned long flags, + int width, + int precision, + int base) +{ + int charsPerThousand; + int length; + double number; + double workNumber; + int integerDigits; + int fractionDigits; + int exponentDigits; + int expectedWidth; + int exponent; + unsigned int uExponent = 0; + double dblBase; + BOOLEAN_T isNegative; + BOOLEAN_T isExponentNegative = FALSE; + BOOLEAN_T isHex; + const char *digits; + char numberBuffer[MAX_MANTISSA_DIGITS + * (1 + MAX_LOCALE_SEPARATOR_LENGTH) + 1]; + char *numberPointer; + char exponentBuffer[MAX_EXPONENT_DIGITS + 1]; + char *exponentPointer = NULL; + int groupingIndex; + char *work; + int i; + BOOLEAN_T onlyzero; + int zeroes = 0; + + assert(VALID(self)); + assert(VALID(self->OutStream)); + assert(base == BASE_DECIMAL || base == BASE_HEX); + + number = (double)longdoubleNumber; + + /* Look for infinite numbers and non-a-number first */ + switch (TrioIsInfinite(number)) + { + case 1: + /* Positive infinity */ + TrioWriteString(self, + (flags & FLAGS_UPPER) + ? INFINITE_UPPER + : INFINITE_LOWER, + flags, width, precision); + return; + + case -1: + /* Negative infinity */ + TrioWriteString(self, + (flags & FLAGS_UPPER) + ? "-" INFINITE_UPPER + : "-" INFINITE_LOWER, + flags, width, precision); + return; + + default: + /* Finitude */ + break; + } + if (TrioIsNan(number)) + { + TrioWriteString(self, + (flags & FLAGS_UPPER) + ? NAN_UPPER + : NAN_LOWER, + flags, width, precision); + return; + } + + /* Normal numbers */ + digits = (flags & FLAGS_UPPER) ? internalDigitsUpper : internalDigitsLower; + isHex = (base == BASE_HEX); + dblBase = (double)base; + + if (precision == NO_PRECISION) + precision = FLT_DIG; + + isNegative = (number < 0.0); + if (isNegative) + number = -number; + + if ((flags & FLAGS_FLOAT_G) || isHex) + { + if (precision == 0) + precision = 1; + + if ((number < 1.0e-4) || (number > pow(10.0, (double)precision))) + { + /* Use scientific notation */ + flags |= FLAGS_FLOAT_E; + } + else if (number < 1.0) + { + /* + * Use normal notation. If the integer part of the number is + * zero, then adjust the precision to include leading fractional + * zeros. + */ + workNumber = fabs(guarded_log10(number)); + if (workNumber - floor(workNumber) < 0.001) + workNumber--; + zeroes = (int)floor(workNumber); + } + } + + if (flags & FLAGS_FLOAT_E) + { + /* Scale the number */ + workNumber = guarded_log10(number); + if (workNumber == -HUGE_VAL) + { + exponent = 0; + /* Undo setting */ + if (flags & FLAGS_FLOAT_G) + flags &= ~FLAGS_FLOAT_E; + } + else + { + exponent = (int)floor(workNumber); + number /= pow(10.0, (double)exponent); + isExponentNegative = (exponent < 0); + uExponent = (isExponentNegative) ? -exponent : exponent; + /* No thousand separators */ + flags &= ~FLAGS_QUOTE; + } + } + + /* + * Truncated number. + * + * precision is number of significant digits for FLOAT_G + * and number of fractional digits for others + */ + integerDigits = (floor(number) > DBL_EPSILON) + ? 1 + (int)guarded_log10(floor(number)) + : 1; + fractionDigits = ((flags & FLAGS_FLOAT_G) && (zeroes == 0)) + ? precision - integerDigits + : zeroes + precision; + + number = floor(0.5 + number * pow(dblBase, (double)fractionDigits)); + workNumber = (isHex + ? guarded_log16(0.5 + number) + : guarded_log10(0.5 + number)); + if ((int)workNumber + 1 > integerDigits + fractionDigits) + { + if (flags & FLAGS_FLOAT_E) + { + /* Adjust if number was rounded up one digit (ie. 0.99 to 1.00) */ + exponent--; + uExponent -= (isExponentNegative) ? 1 : -1; + number /= dblBase; + } + else + { + /* Adjust if number was rounded up one digit (ie. 99 to 100) */ + integerDigits++; + } + } + + /* Build the fraction part */ + numberPointer = &numberBuffer[sizeof(numberBuffer) - 1]; + *numberPointer = NIL; + onlyzero = TRUE; + for (i = 0; i < fractionDigits; i++) + { + *(--numberPointer) = digits[(int)fmod(number, dblBase)]; + number = floor(number / dblBase); + + if ((flags & FLAGS_FLOAT_G) && !(flags & FLAGS_ALTERNATIVE)) + { + /* Prune trailing zeroes */ + if (numberPointer[0] != digits[0]) + onlyzero = FALSE; + else if (onlyzero && (numberPointer[0] == digits[0])) + numberPointer++; + } + else + onlyzero = FALSE; + } + + /* Insert decimal point */ + if ((flags & FLAGS_ALTERNATIVE) || ((fractionDigits > 0) && !onlyzero)) + { + i = StrLength(internalDecimalPoint); + while (i> 0) + { + *(--numberPointer) = internalDecimalPoint[--i]; + } + } + /* Insert the integer part and thousand separators */ + charsPerThousand = (int)internalGrouping[0]; + groupingIndex = 1; + for (i = 1; i < integerDigits + 1; i++) + { + *(--numberPointer) = digits[(int)fmod(number, dblBase)]; + number = floor(number / dblBase); + if (number < DBL_EPSILON) + break; + + if ((i > 0) + && ((flags & (FLAGS_FLOAT_E | FLAGS_QUOTE)) == FLAGS_QUOTE) + && (charsPerThousand != NO_GROUPING) + && (i % charsPerThousand == 0)) + { + /* + * We are building the number from the least significant + * to the most significant digit, so we have to copy the + * thousand separator backwards + */ + length = StrLength(internalThousandSeparator); + integerDigits += length; + if (((int)(numberPointer - numberBuffer) - length) > 0) + { + work = &internalThousandSeparator[length - 1]; + while (length-- > 0) + *(--numberPointer) = *work--; + } + + /* Advance to next grouping number */ + if (charsPerThousand != NO_GROUPING) + { + switch (internalGrouping[groupingIndex]) + { + case CHAR_MAX: /* Disable grouping */ + charsPerThousand = NO_GROUPING; + break; + case 0: /* Repeat last group */ + break; + default: + charsPerThousand = (int)internalGrouping[groupingIndex++]; + break; + } + } + } + } + + /* Build the exponent */ + exponentDigits = 0; + if (flags & FLAGS_FLOAT_E) + { + exponentPointer = &exponentBuffer[sizeof(exponentBuffer) - 1]; + *exponentPointer-- = NIL; + do { + *exponentPointer-- = digits[uExponent % base]; + uExponent /= base; + exponentDigits++; + } while (uExponent); + } + + /* + * Calculate expected width. + * sign + integer part + thousands separators + decimal point + * + fraction + exponent + */ + expectedWidth = StrLength(numberPointer); + if (isNegative || (flags & FLAGS_SHOWSIGN)) + expectedWidth += sizeof("-") - 1; + if (exponentDigits > 0) + expectedWidth += exponentDigits + + ((exponentDigits > 1) ? sizeof("E+") : sizeof("E+0")) - 1; + if (isHex) + expectedWidth += sizeof("0X") - 1; + + /* Output prefixing */ + if (flags & FLAGS_NILPADDING) + { + /* Leading zeros must be after sign */ + if (isNegative) + self->OutStream(self, '-'); + else if (flags & FLAGS_SHOWSIGN) + self->OutStream(self, '+'); + if (isHex) + { + self->OutStream(self, '0'); + self->OutStream(self, (flags & FLAGS_UPPER) ? 'X' : 'x'); + } + if (!(flags & FLAGS_LEFTADJUST)) + { + for (i = expectedWidth; i < width; i++) + { + self->OutStream(self, '0'); + } + } + } + else + { + /* Leading spaces must be before sign */ + if (!(flags & FLAGS_LEFTADJUST)) + { + for (i = expectedWidth; i < width; i++) + { + self->OutStream(self, CHAR_ADJUST); + } + } + if (isNegative) + self->OutStream(self, '-'); + else if (flags & FLAGS_SHOWSIGN) + self->OutStream(self, '+'); + if (isHex) + { + self->OutStream(self, '0'); + self->OutStream(self, (flags & FLAGS_UPPER) ? 'X' : 'x'); + } + } + /* Output number */ + for (i = 0; numberPointer[i]; i++) + { + self->OutStream(self, numberPointer[i]); + } + /* Output exponent */ + if (exponentDigits > 0) + { + self->OutStream(self, + isHex + ? ((flags & FLAGS_UPPER) ? 'P' : 'p') + : ((flags & FLAGS_UPPER) ? 'E' : 'e')); + self->OutStream(self, (isExponentNegative) ? '-' : '+'); + + /* The exponent must contain at least two digits */ + if (exponentDigits == 1) + self->OutStream(self, '0'); + + for (i = 0; i < exponentDigits; i++) + { + self->OutStream(self, exponentPointer[i + 1]); + } + } + /* Output trailing spaces */ + if (flags & FLAGS_LEFTADJUST) + { + for (i = expectedWidth; i < width; i++) + { + self->OutStream(self, CHAR_ADJUST); + } + } +} + +/************************************************************************* + * TrioFormatProcess [private] + */ +static int +TrioFormatProcess(trio_T *data, + const char *format, + parameter_T *parameters) + +{ +#if defined(TRIO_COMPILER_SUPPORTS_MULTIBYTE) + int charlen; +#endif + int i; + const char *string; + void *pointer; + unsigned long flags; + int width; + int precision; + int base; + int index; + + index = 0; + i = 0; +#if defined(TRIO_COMPILER_SUPPORTS_MULTIBYTE) + mblen(NULL, 0); +#endif + + while (format[index]) + { +#if defined(TRIO_COMPILER_SUPPORTS_MULTIBYTE) + if (! isascii(format[index])) + { + charlen = mblen(&format[index], MB_LEN_MAX); + while (charlen-- > 0) + { + data->OutStream(data, format[index++]); + } + continue; /* while */ + } +#endif /* TRIO_COMPILER_SUPPORTS_MULTIBYTE */ + if (CHAR_IDENTIFIER == format[index]) + { + if (CHAR_IDENTIFIER == format[index + 1]) + { + data->OutStream(data, CHAR_IDENTIFIER); + index += 2; + } + else + { + /* Skip the parameter entries */ + while (parameters[i].type == FORMAT_PARAMETER) + i++; + + flags = parameters[i].flags; + + /* Find width */ + width = parameters[i].width; + if (flags & FLAGS_WIDTH_PARAMETER) + { + /* Get width from parameter list */ + width = (int)parameters[width].data.number.as_signed; + } + + /* Find precision */ + if (flags & FLAGS_PRECISION) + { + precision = parameters[i].precision; + if (flags & FLAGS_PRECISION_PARAMETER) + { + /* Get precision from parameter list */ + precision = (int)parameters[precision].data.number.as_signed; + } + } + else + { + precision = NO_PRECISION; + } + + /* Find base */ + base = parameters[i].base; + if (flags & FLAGS_BASE_PARAMETER) + { + /* Get base from parameter list */ + base = (int)parameters[base].data.number.as_signed; + } + + switch (parameters[i].type) + { + case FORMAT_CHAR: + if (flags & FLAGS_QUOTE) + data->OutStream(data, CHAR_QUOTE); + if (! (flags & FLAGS_LEFTADJUST)) + { + while (--width > 0) + data->OutStream(data, CHAR_ADJUST); + } +#if TRIO_WIDECHAR + if (flags & FLAGS_WIDECHAR) + { + TrioWriteWideStringCharacter(data, + (wchar_t)parameters[i].data.number.as_signed, + flags, + NO_WIDTH); + } + else +#endif + TrioWriteStringCharacter(data, + (int)parameters[i].data.number.as_signed, + flags); + + if (flags & FLAGS_LEFTADJUST) + { + while(--width > 0) + data->OutStream(data, CHAR_ADJUST); + } + if (flags & FLAGS_QUOTE) + data->OutStream(data, CHAR_QUOTE); + + break; /* FORMAT_CHAR */ + + case FORMAT_INT: + if (base == NO_BASE) + base = BASE_DECIMAL; + + TrioWriteNumber(data, + parameters[i].data.number.as_signed, + flags, + width, + precision, + base); + + break; /* FORMAT_INT */ + + case FORMAT_DOUBLE: + TrioWriteDouble(data, + parameters[i].data.longdoubleNumber, + flags, + width, + precision, + base); + break; /* FORMAT_DOUBLE */ + + case FORMAT_STRING: +#if TRIO_WIDECHAR + if (flags & FLAGS_WIDECHAR) + { + TrioWriteWideString(data, + parameters[i].data.wstring, + flags, + width, + precision); + } + else +#endif +#ifdef QUALIFIER_ESCAPE + { + char *s = NULL; + static const char* empty = "(null)"; + switch (parameters[i].escape) + { + case ESCAPE_ULM: + s = glite_lbu_EscapeULM(parameters[i].data.string); + break; + case ESCAPE_XML: + s = glite_lbu_EscapeXML(parameters[i].data.string); + break; + case ESCAPE_SQL: + s = glite_lbu_EscapeSQL(parameters[i].data.string); + break; + case ESCAPE_NONE: + s = strdup(parameters[i].data.string ? parameters[i].data.string : empty); + break; + } + TrioWriteString(data,s,flags,width,precision); + free(s); + } +#else + { + TrioWriteString(data, + parameters[i].data.string, + flags, + width, + precision); + } +#endif + break; /* FORMAT_STRING */ + + case FORMAT_POINTER: + { + reference_T reference; + + reference.data = data; + reference.parameter = ¶meters[i]; + trio_print_pointer(&reference, parameters[i].data.pointer); + } + break; /* FORMAT_POINTER */ + + case FORMAT_COUNT: + pointer = parameters[i].data.pointer; + if (NULL != pointer) + { + /* + * C99 paragraph 7.19.6.1.8 says "the number of + * characters written to the output stream so far by + * this call", which is data->committed + */ +#if defined(QUALIFIER_SIZE_T) || defined(QUALIFIER_SIZE_T_UPPER) + if (flags & FLAGS_SIZE_T) + *(size_t *)pointer = (size_t)data->committed; + else +#endif +#if defined(QUALIFIER_PTRDIFF_T) + if (flags & FLAGS_PTRDIFF_T) + *(ptrdiff_t *)pointer = (ptrdiff_t)data->committed; + else +#endif +#if defined(QUALIFIER_INTMAX_T) + if (flags & FLAGS_INTMAX_T) + *(trio_intmax_t *)pointer = (trio_intmax_t)data->committed; + else +#endif + if (flags & FLAGS_QUAD) + { + *(trio_ulonglong_t *)pointer = (trio_ulonglong_t)data->committed; + } + else if (flags & FLAGS_LONG) + { + *(long int *)pointer = (long int)data->committed; + } + else if (flags & FLAGS_SHORT) + { + *(short int *)pointer = (short int)data->committed; + } + else + { + *(int *)pointer = (int)data->committed; + } + } + break; /* FORMAT_COUNT */ + + case FORMAT_PARAMETER: + break; /* FORMAT_PARAMETER */ + +#if defined(FORMAT_ERRNO) + case FORMAT_ERRNO: + string = StrError(parameters[i].data.errorNumber); + if (string) + { + TrioWriteString(data, + string, + flags, + width, + precision); + } + else + { + data->OutStream(data, '#'); + TrioWriteNumber(data, + (trio_intmax_t)parameters[i].data.errorNumber, + flags, + width, + precision, + BASE_DECIMAL); + } + break; /* FORMAT_ERRNO */ +#endif /* defined(FORMAT_ERRNO) */ + +#if defined(FORMAT_USER_DEFINED) + case FORMAT_USER_DEFINED: + { + reference_T reference; + userdef_T *def = NULL; + + if (parameters[i].user_name[0] == NIL) + { + /* Use handle */ + if ((i > 0) || + (parameters[i - 1].type == FORMAT_PARAMETER)) + def = (userdef_T *)parameters[i - 1].data.pointer; + } + else + { + /* Look up namespace */ + def = TrioFindNamespace(parameters[i].user_name, NULL); + } + if (def) { + reference.data = data; + reference.parameter = ¶meters[i]; + def->callback(&reference); + } + } + break; +#endif /* defined(FORMAT_USER_DEFINED) */ + + default: + break; + } /* switch parameter type */ + + /* Prepare for next */ + index = parameters[i].indexAfterSpecifier; + i++; + } + } + else /* not identifier */ + { + data->OutStream(data, format[index++]); + } + } + return data->processed; +} + +/************************************************************************* + * TrioFormatRef [private] + */ +static int +TrioFormatRef(reference_T *reference, + const char *format, + va_list arglist, + void **argarray) +{ + int status; + parameter_T parameters[MAX_PARAMETERS]; + + status = TrioPreprocess(TYPE_PRINT, format, parameters, arglist, argarray); + if (status < 0) + return status; + + return TrioFormatProcess(reference->data, format, parameters); +} + +/************************************************************************* + * TrioFormat [private] + * + * Description: + * This is the main engine for formatting output + */ +static int +TrioFormat(void *destination, + size_t destinationSize, + void (*OutStream)(trio_T *, int), + const char *format, + va_list arglist, + void **argarray) +{ + int status; + trio_T data; + parameter_T parameters[MAX_PARAMETERS]; + + assert(VALID(OutStream)); + assert(VALID(format)); + + memset(&data, 0, sizeof(data)); + data.OutStream = OutStream; + data.location = destination; + data.max = destinationSize; + +#if defined(USE_LOCALE) + if (NULL == internalLocaleValues) + { + TrioSetLocale(); + } +#endif + + status = TrioPreprocess(TYPE_PRINT, format, parameters, arglist, argarray); + if (status < 0) + return status; + + return TrioFormatProcess(&data, format, parameters); +} + +/************************************************************************* + * TrioOutStreamFile [private] + */ +static void +TrioOutStreamFile(trio_T *self, + int output) +{ + FILE *file = (FILE *)self->location; + + assert(VALID(self)); + assert(VALID(file)); + + self->processed++; + self->committed++; + (void)fputc(output, file); +} + +/************************************************************************* + * TrioOutStreamFileDescriptor [private] + */ +static void +TrioOutStreamFileDescriptor(trio_T *self, + int output) +{ + int fd = *((int *)self->location); + char ch; + + assert(VALID(self)); + + ch = (char)output; + (void)write(fd, &ch, sizeof(char)); + self->processed++; + self->committed++; +} + +/************************************************************************* + * TrioOutStreamString [private] + */ +static void +TrioOutStreamString(trio_T *self, + int output) +{ + char **buffer = (char **)self->location; + + assert(VALID(self)); + assert(VALID(buffer)); + + **buffer = (char)output; + (*buffer)++; + self->processed++; + self->committed++; +} + +/************************************************************************* + * TrioOutStreamStringMax [private] + */ +static void +TrioOutStreamStringMax(trio_T *self, + int output) +{ + char **buffer; + + assert(VALID(self)); + buffer = (char **)self->location; + assert(VALID(buffer)); + + if (self->processed < self->max) + { + **buffer = (char)output; + (*buffer)++; + self->committed++; + } + self->processed++; +} + +/************************************************************************* + * TrioOutStreamStringDynamic [private] + */ +#define DYNAMIC_START_SIZE 32 +struct dynamicBuffer { + char *buffer; + size_t length; + size_t allocated; +}; + +static void +TrioOutStreamStringDynamic(trio_T *self, + int output) +{ + struct dynamicBuffer *infop; + + assert(VALID(self)); + assert(VALID(self->location)); + + infop = (struct dynamicBuffer *)self->location; + + if (infop->buffer == NULL) + { + /* Start with a reasonable size */ + infop->buffer = (char *)TRIO_MALLOC(DYNAMIC_START_SIZE); + if (infop->buffer == NULL) + return; /* fail */ + + infop->allocated = DYNAMIC_START_SIZE; + self->processed = 0; + self->committed = 0; + } + else if (self->committed + sizeof(NIL) >= infop->allocated) + { + char *newptr; + + /* Allocate increasing chunks */ + newptr = (char *)TRIO_REALLOC(infop->buffer, infop->allocated * 2); + + if (newptr == NULL) + return; + + infop->buffer = newptr; + infop->allocated *= 2; + } + + infop->buffer[self->committed] = (char)output; + self->committed++; + self->processed++; + + infop->length = self->committed; +} + +/************************************************************************* + * printf + */ +int +trio_printf(const char *format, + ...) +{ + int status; + va_list args; + + assert(VALID(format)); + + va_start(args, format); + status = TrioFormat(stdout, 0, TrioOutStreamFile, format, args, NULL); + va_end(args); + return status; +} + +int +trio_vprintf(const char *format, + va_list args) +{ + assert(VALID(format)); + + return TrioFormat(stdout, 0, TrioOutStreamFile, format, args, NULL); +} + +#ifdef __GNUC__ +#define UNUSED_VAR __attribute__((unused)) +#else +#define UNUSED_VAR +#endif + +static void shutup_unitialized(va_list *dummy UNUSED_VAR) { +} + +int +trio_printfv(const char *format, + void ** args) +{ + va_list dummy; + shutup_unitialized(&dummy); + + assert(VALID(format)); + + return TrioFormat(stdout, 0, TrioOutStreamFile, format, dummy, args); +} + +/************************************************************************* + * fprintf + */ +int +trio_fprintf(FILE *file, + const char *format, + ...) +{ + int status; + va_list args; + + assert(VALID(file)); + assert(VALID(format)); + + va_start(args, format); + status = TrioFormat(file, 0, TrioOutStreamFile, format, args, NULL); + va_end(args); + return status; +} + +int +trio_vfprintf(FILE *file, + const char *format, + va_list args) +{ + assert(VALID(file)); + assert(VALID(format)); + + return TrioFormat(file, 0, TrioOutStreamFile, format, args, NULL); +} + +int +trio_fprintfv(FILE *file, + const char *format, + void ** args) +{ + va_list dummy; + shutup_unitialized(&dummy); + + assert(VALID(file)); + assert(VALID(format)); + + return TrioFormat(file, 0, TrioOutStreamFile, format, dummy, args); +} + +/************************************************************************* + * dprintf + */ +int +trio_dprintf(int fd, + const char *format, + ...) +{ + int status; + va_list args; + + assert(VALID(format)); + + va_start(args, format); + status = TrioFormat(&fd, 0, TrioOutStreamFileDescriptor, format, args, NULL); + va_end(args); + return status; +} + +int +trio_vdprintf(int fd, + const char *format, + va_list args) +{ + assert(VALID(format)); + + return TrioFormat(&fd, 0, TrioOutStreamFileDescriptor, format, args, NULL); +} + +int +trio_dprintfv(int fd, + const char *format, + void **args) +{ + va_list dummy; + shutup_unitialized(&dummy); + + assert(VALID(format)); + + return TrioFormat(&fd, 0, TrioOutStreamFileDescriptor, format, dummy, args); +} + +/************************************************************************* + * sprintf + */ +int +trio_sprintf(char *buffer, + const char *format, + ...) +{ + int status; + va_list args; + + assert(VALID(buffer)); + assert(VALID(format)); + + va_start(args, format); + status = TrioFormat(&buffer, 0, TrioOutStreamString, format, args, NULL); + *buffer = NIL; /* Terminate with NIL character */ + va_end(args); + return status; +} + +int +trio_vsprintf(char *buffer, + const char *format, + va_list args) +{ + int status; + + assert(VALID(buffer)); + assert(VALID(format)); + + status = TrioFormat(&buffer, 0, TrioOutStreamString, format, args, NULL); + *buffer = NIL; + return status; +} + +int +trio_sprintfv(char *buffer, + const char *format, + void **args) +{ + int status; + va_list dummy; + shutup_unitialized(&dummy); + + assert(VALID(buffer)); + assert(VALID(format)); + + status = TrioFormat(&buffer, 0, TrioOutStreamString, format, dummy, args); + *buffer = NIL; + return status; +} + +/************************************************************************* + * snprintf + */ +int +trio_snprintf(char *buffer, + size_t bufferSize, + const char *format, + ...) +{ + int status; + va_list args; + + assert(VALID(buffer)); + assert(VALID(format)); + + va_start(args, format); + status = TrioFormat(&buffer, bufferSize > 0 ? bufferSize - 1 : 0, + TrioOutStreamStringMax, format, args, NULL); + if (bufferSize > 0) + *buffer = NIL; + va_end(args); + return status; +} + +int +trio_vsnprintf(char *buffer, + size_t bufferSize, + const char *format, + va_list args) +{ + int status; + + assert(VALID(buffer)); + assert(VALID(format)); + + status = TrioFormat(&buffer, bufferSize > 0 ? bufferSize - 1 : 0, + TrioOutStreamStringMax, format, args, NULL); + if (bufferSize > 0) + *buffer = NIL; + return status; +} + +int +trio_snprintfv(char *buffer, + size_t bufferSize, + const char *format, + void **args) +{ + int status; + va_list dummy; + shutup_unitialized(&dummy); + + assert(VALID(buffer)); + assert(VALID(format)); + + status = TrioFormat(&buffer, bufferSize > 0 ? bufferSize - 1 : 0, + TrioOutStreamStringMax, format, dummy, args); + if (bufferSize > 0) + *buffer = NIL; + return status; +} + +/************************************************************************* + * snprintfcat + * Appends the new string to the buffer string overwriting the '\0' + * character at the end of buffer. + */ +int +trio_snprintfcat(char *buffer, + size_t bufferSize, + const char *format, + ...) +{ + int status; + va_list args; + size_t buf_len; + + va_start(args, format); + + assert(VALID(buffer)); + assert(VALID(format)); + + buf_len = strlen(buffer); + buffer = &buffer[buf_len]; + + status = TrioFormat(&buffer, bufferSize - 1 - buf_len, + TrioOutStreamStringMax, format, args, NULL); + va_end(args); + *buffer = NIL; + return status; +} + +int +trio_vsnprintfcat(char *buffer, + size_t bufferSize, + const char *format, + va_list args) +{ + int status; + size_t buf_len; + assert(VALID(buffer)); + assert(VALID(format)); + + buf_len = strlen(buffer); + buffer = &buffer[buf_len]; + status = TrioFormat(&buffer, bufferSize - 1 - buf_len, + TrioOutStreamStringMax, format, args, NULL); + *buffer = NIL; + return status; +} + +/************************************************************************* + * trio_aprintf + */ + +/* Deprecated */ +char * +trio_aprintf(const char *format, + ...) +{ + va_list args; + struct dynamicBuffer info; + + assert(VALID(format)); + + info.buffer = NULL; + info.length = 0; + info.allocated = 0; + + va_start(args, format); + (void)TrioFormat(&info, 0, TrioOutStreamStringDynamic, format, args, NULL); + va_end(args); + if (info.length) { + info.buffer[info.length] = NIL; /* we terminate this with a zero byte */ + return info.buffer; + } + else + return NULL; +} + +/* Deprecated */ +char * +trio_vaprintf(const char *format, + va_list args) +{ + struct dynamicBuffer info; + + assert(VALID(format)); + + info.buffer = NULL; + info.length = 0; + info.allocated = 0; + + (void)TrioFormat(&info, 0, TrioOutStreamStringDynamic, format, args, NULL); + if (info.length) { + info.buffer[info.length] = NIL; /* we terminate this with a zero byte */ + return info.buffer; + } + else + return NULL; +} + +int +trio_asprintf(char **result, + const char *format, + ...) +{ + va_list args; + int status; + struct dynamicBuffer info; + + assert(VALID(format)); + + info.buffer = NULL; + info.length = 0; + info.allocated = 0; + + va_start(args, format); + status = TrioFormat(&info, 0, TrioOutStreamStringDynamic, format, args, NULL); + va_end(args); + if (status < 0) { + *result = NULL; + return status; + } + if (info.length == 0) { + /* + * If the length is zero, no characters have been written and therefore + * no memory has been allocated, but we must to allocate and return an + * empty string. + */ + info.buffer = (char *)TRIO_MALLOC(sizeof(char)); + if (info.buffer == NULL) { + *result = NULL; + return TRIO_ERROR_RETURN(TRIO_ENOMEM, 0); + } + } + info.buffer[info.length] = NIL; /* we terminate this with a zero byte */ + *result = info.buffer; + + return status; +} + +int +trio_vasprintf(char **result, + const char *format, + va_list args) +{ + int status; + struct dynamicBuffer info; + + assert(VALID(format)); + + info.buffer = NULL; + info.length = 0; + info.allocated = 0; + + status = TrioFormat(&info, 0, TrioOutStreamStringDynamic, format, args, NULL); + if (status < 0) { + *result = NULL; + return status; + } + if (info.length == 0) { + info.buffer = (char *)TRIO_MALLOC(sizeof(char)); + if (info.buffer == NULL) { + *result = NULL; + return TRIO_ERROR_RETURN(TRIO_ENOMEM, 0); + } + } + info.buffer[info.length] = NIL; /* we terminate this with a zero byte */ + *result = info.buffer; + + return status; +} + + +/************************************************************************* + * + * @CALLBACK + * + ************************************************************************/ + + +/************************************************************************* + * trio_register [public] + */ +void * +trio_register(trio_callback_t callback, + const char *name) +{ + userdef_T *def; + userdef_T *prev = NULL; + + if (callback == NULL) + return NULL; + + if (name) + { + /* Handle built-in namespaces */ + if (name[0] == ':') + { + if (StrEqual(name, ":enter")) + { + internalEnterCriticalRegion = callback; + } + else if (StrEqual(name, ":leave")) + { + internalLeaveCriticalRegion = callback; + } + return NULL; + } + + /* Bail out if namespace is too long */ + if (StrLength(name) >= MAX_USER_NAME) + return NULL; + + /* Bail out if namespace already is registered */ + def = TrioFindNamespace(name, &prev); + if (def) + return NULL; + } + + def = (userdef_T *)TRIO_MALLOC(sizeof(userdef_T)); + if (def) + { + if (internalEnterCriticalRegion) + (void)internalEnterCriticalRegion(NULL); + + if (name) + { + /* Link into internal list */ + if (prev == NULL) + internalUserDef = def; + else + prev->next = def; + } + /* Initialize */ + def->callback = callback; + def->name = (name == NULL) + ? NULL + : StrDuplicate(name); + def->next = NULL; + + if (internalLeaveCriticalRegion) + (void)internalLeaveCriticalRegion(NULL); + } + return def; +} + +/************************************************************************* + * trio_unregister [public] + */ +void +trio_unregister(void *handle) +{ + userdef_T *self = (userdef_T *)handle; + userdef_T *def; + userdef_T *prev = NULL; + + assert(VALID(self)); + + if (self->name) + { + def = TrioFindNamespace(self->name, &prev); + if (def) + { + if (internalEnterCriticalRegion) + (void)internalEnterCriticalRegion(NULL); + + if (prev == NULL) + internalUserDef = NULL; + else + prev->next = def->next; + + if (internalLeaveCriticalRegion) + (void)internalLeaveCriticalRegion(NULL); + } + StrFree(self->name); + } + TRIO_FREE(self); +} + +/************************************************************************* + * trio_get_format [public] + */ +const char * +trio_get_format(void *ref) +{ + assert(((reference_T *)ref)->parameter->type == FORMAT_USER_DEFINED); + + return (((reference_T *)ref)->parameter->user_data); +} + +/************************************************************************* + * trio_get_argument [public] + */ +void * +trio_get_argument(void *ref) +{ + assert(((reference_T *)ref)->parameter->type == FORMAT_USER_DEFINED); + + return ((reference_T *)ref)->parameter->data.pointer; +} + +/************************************************************************* + * trio_get_width / trio_set_width [public] + */ +int +trio_get_width(void *ref) +{ + return ((reference_T *)ref)->parameter->width; +} + +void +trio_set_width(void *ref, + int width) +{ + ((reference_T *)ref)->parameter->width = width; +} + +/************************************************************************* + * trio_get_precision / trio_set_precision [public] + */ +int +trio_get_precision(void *ref) +{ + return (((reference_T *)ref)->parameter->precision); +} + +void +trio_set_precision(void *ref, + int precision) +{ + ((reference_T *)ref)->parameter->precision = precision; +} + +/************************************************************************* + * trio_get_base / trio_set_base [public] + */ +int +trio_get_base(void *ref) +{ + return (((reference_T *)ref)->parameter->base); +} + +void +trio_set_base(void *ref, + int base) +{ + ((reference_T *)ref)->parameter->base = base; +} + +/************************************************************************* + * trio_get_long / trio_set_long [public] + */ +int +trio_get_long(void *ref) +{ + return (((reference_T *)ref)->parameter->flags & FLAGS_LONG); +} + +void +trio_set_long(void *ref, + int is_long) +{ + if (is_long) + ((reference_T *)ref)->parameter->flags |= FLAGS_LONG; + else + ((reference_T *)ref)->parameter->flags &= ~FLAGS_LONG; +} + +/************************************************************************* + * trio_get_longlong / trio_set_longlong [public] + */ +int +trio_get_longlong(void *ref) +{ + return (((reference_T *)ref)->parameter->flags & FLAGS_QUAD); +} + +void +trio_set_longlong(void *ref, + int is_longlong) +{ + if (is_longlong) + ((reference_T *)ref)->parameter->flags |= FLAGS_QUAD; + else + ((reference_T *)ref)->parameter->flags &= ~FLAGS_QUAD; +} + +/************************************************************************* + * trio_get_longdouble / trio_set_longdouble [public] + */ +int +trio_get_longdouble(void *ref) +{ + return (((reference_T *)ref)->parameter->flags & FLAGS_LONGDOUBLE); +} + +void +trio_set_longdouble(void *ref, + int is_longdouble) +{ + if (is_longdouble) + ((reference_T *)ref)->parameter->flags |= FLAGS_LONGDOUBLE; + else + ((reference_T *)ref)->parameter->flags &= ~FLAGS_LONGDOUBLE; +} + +/************************************************************************* + * trio_get_short / trio_set_short [public] + */ +int +trio_get_short(void *ref) +{ + return (((reference_T *)ref)->parameter->flags & FLAGS_SHORT); +} + +void +trio_set_short(void *ref, + int is_short) +{ + if (is_short) + ((reference_T *)ref)->parameter->flags |= FLAGS_SHORT; + else + ((reference_T *)ref)->parameter->flags &= ~FLAGS_SHORT; +} + +/************************************************************************* + * trio_get_shortshort / trio_set_shortshort [public] + */ +int +trio_get_shortshort(void *ref) +{ + return (((reference_T *)ref)->parameter->flags & FLAGS_SHORTSHORT); +} + +void +trio_set_shortshort(void *ref, + int is_shortshort) +{ + if (is_shortshort) + ((reference_T *)ref)->parameter->flags |= FLAGS_SHORTSHORT; + else + ((reference_T *)ref)->parameter->flags &= ~FLAGS_SHORTSHORT; +} + +/************************************************************************* + * trio_get_alternative / trio_set_alternative [public] + */ +int +trio_get_alternative(void *ref) +{ + return (((reference_T *)ref)->parameter->flags & FLAGS_ALTERNATIVE); +} + +void +trio_set_alternative(void *ref, + int is_alternative) +{ + if (is_alternative) + ((reference_T *)ref)->parameter->flags |= FLAGS_ALTERNATIVE; + else + ((reference_T *)ref)->parameter->flags &= ~FLAGS_ALTERNATIVE; +} + +/************************************************************************* + * trio_get_alignment / trio_set_alignment [public] + */ +int +trio_get_alignment(void *ref) +{ + return (((reference_T *)ref)->parameter->flags & FLAGS_LEFTADJUST); +} + +void +trio_set_alignment(void *ref, + int is_leftaligned) +{ + if (is_leftaligned) + ((reference_T *)ref)->parameter->flags |= FLAGS_LEFTADJUST; + else + ((reference_T *)ref)->parameter->flags &= ~FLAGS_LEFTADJUST; +} + +/************************************************************************* + * trio_get_spacing /trio_set_spacing [public] + */ +int +trio_get_spacing(void *ref) +{ + return (((reference_T *)ref)->parameter->flags & FLAGS_SPACE); +} + +void +trio_set_spacing(void *ref, + int is_space) +{ + if (is_space) + ((reference_T *)ref)->parameter->flags |= FLAGS_SPACE; + else + ((reference_T *)ref)->parameter->flags &= ~FLAGS_SPACE; +} + +/************************************************************************* + * trio_get_sign / trio_set_sign [public] + */ +int +trio_get_sign(void *ref) +{ + return (((reference_T *)ref)->parameter->flags & FLAGS_SHOWSIGN); +} + +void +trio_set_sign(void *ref, + int is_sign) +{ + if (is_sign) + ((reference_T *)ref)->parameter->flags |= FLAGS_SHOWSIGN; + else + ((reference_T *)ref)->parameter->flags &= ~FLAGS_SHOWSIGN; +} + +/************************************************************************* + * trio_get_padding / trio_set_padding [public] + */ +int +trio_get_padding(void *ref) +{ + return (((reference_T *)ref)->parameter->flags & FLAGS_NILPADDING); +} + +void +trio_set_padding(void *ref, + int is_padding) +{ + if (is_padding) + ((reference_T *)ref)->parameter->flags |= FLAGS_NILPADDING; + else + ((reference_T *)ref)->parameter->flags &= ~FLAGS_NILPADDING; +} + +/************************************************************************* + * trio_get_quote / trio_set_quote [public] + */ +int +trio_get_quote(void *ref) +{ + return (((reference_T *)ref)->parameter->flags & FLAGS_QUOTE); +} + +void +trio_set_quote(void *ref, + int is_quote) +{ + if (is_quote) + ((reference_T *)ref)->parameter->flags |= FLAGS_QUOTE; + else + ((reference_T *)ref)->parameter->flags &= ~FLAGS_QUOTE; +} + +/************************************************************************* + * trio_get_upper / trio_set_upper [public] + */ +int +trio_get_upper(void *ref) +{ + return (((reference_T *)ref)->parameter->flags & FLAGS_UPPER); +} + +void +trio_set_upper(void *ref, + int is_upper) +{ + if (is_upper) + ((reference_T *)ref)->parameter->flags |= FLAGS_UPPER; + else + ((reference_T *)ref)->parameter->flags &= ~FLAGS_UPPER; +} + +/************************************************************************* + * trio_get_largest / trio_set_largest [public] + */ +#if TRIO_C99 +int +trio_get_largest(void *ref) +{ + return (((reference_T *)ref)->parameter->flags & FLAGS_INTMAX_T); +} + +void +trio_set_largest(void *ref, + int is_largest) +{ + if (is_largest) + ((reference_T *)ref)->parameter->flags |= FLAGS_INTMAX_T; + else + ((reference_T *)ref)->parameter->flags &= ~FLAGS_INTMAX_T; +} +#endif + +/************************************************************************* + * trio_get_ptrdiff / trio_set_ptrdiff [public] + */ +int +trio_get_ptrdiff(void *ref) +{ + return (((reference_T *)ref)->parameter->flags & FLAGS_PTRDIFF_T); +} + +void +trio_set_ptrdiff(void *ref, + int is_ptrdiff) +{ + if (is_ptrdiff) + ((reference_T *)ref)->parameter->flags |= FLAGS_PTRDIFF_T; + else + ((reference_T *)ref)->parameter->flags &= ~FLAGS_PTRDIFF_T; +} + +/************************************************************************* + * trio_get_size / trio_set_size [public] + */ +#if TRIO_C99 +int +trio_get_size(void *ref) +{ + return (((reference_T *)ref)->parameter->flags & FLAGS_SIZE_T); +} + +void +trio_set_size(void *ref, + int is_size) +{ + if (is_size) + ((reference_T *)ref)->parameter->flags |= FLAGS_SIZE_T; + else + ((reference_T *)ref)->parameter->flags &= ~FLAGS_SIZE_T; +} +#endif + +/************************************************************************* + * trio_print_int [public] + */ +void +trio_print_int(void *ref, + int number) +{ + reference_T *self = (reference_T *)ref; + + TrioWriteNumber(self->data, + (trio_intmax_t)number, + self->parameter->flags, + self->parameter->width, + self->parameter->precision, + self->parameter->base); +} + +/************************************************************************* + * trio_print_uint [public] + */ +void +trio_print_uint(void *ref, + unsigned int number) +{ + reference_T *self = (reference_T *)ref; + + TrioWriteNumber(self->data, + (trio_intmax_t)number, + self->parameter->flags | FLAGS_UNSIGNED, + self->parameter->width, + self->parameter->precision, + self->parameter->base); +} + +/************************************************************************* + * trio_print_double [public] + */ +void +trio_print_double(void *ref, + double number) +{ + reference_T *self = (reference_T *)ref; + + TrioWriteDouble(self->data, + number, + self->parameter->flags, + self->parameter->width, + self->parameter->precision, + self->parameter->base); +} + +/************************************************************************* + * trio_print_string [public] + */ +void +trio_print_string(void *ref, + char *string) +{ + reference_T *self = (reference_T *)ref; + + TrioWriteString(self->data, + string, + self->parameter->flags, + self->parameter->width, + self->parameter->precision); +} + +/************************************************************************* + * trio_print_pointer [public] + */ +void +trio_print_pointer(void *ref, + void *pointer) +{ + reference_T *self = (reference_T *)ref; + unsigned long flags; + trio_uintmax_t number; + + if (NULL == pointer) + { + const char *string = null; + while (*string) + self->data->OutStream(self->data, *string++); + } + else + { + /* + * The subtraction of the null pointer is a workaround + * to avoid a compiler warning. The performance overhead + * is negligible (and likely to be removed by an + * optimising compiler). The (char *) casting is done + * to please ANSI C++. + */ + number = (trio_uintmax_t)((char *)pointer - (char *)0); + /* Shrink to size of pointer */ + number &= (trio_uintmax_t)-1; + flags = self->parameter->flags; + flags |= (FLAGS_UNSIGNED | FLAGS_ALTERNATIVE | + FLAGS_NILPADDING); + TrioWriteNumber(self->data, + (trio_intmax_t)number, + flags, + POINTER_WIDTH, + NO_PRECISION, + BASE_HEX); + } +} + +/************************************************************************* + * trio_print_ref [public] + */ +int +trio_print_ref(void *ref, + const char *format, + ...) +{ + int status; + va_list arglist; + + assert(VALID(format)); + + va_start(arglist, format); + status = TrioFormatRef((reference_T *)ref, format, arglist, NULL); + va_end(arglist); + return status; +} + +/************************************************************************* + * trio_vprint_ref [public] + */ +int +trio_vprint_ref(void *ref, + const char *format, + va_list arglist) +{ + assert(VALID(format)); + + return TrioFormatRef((reference_T *)ref, format, arglist, NULL); +} + +/************************************************************************* + * trio_printv_ref [public] + */ +int +trio_printv_ref(void *ref, + const char *format, + void **argarray) +{ + va_list dummy; + shutup_unitialized(&dummy); + + assert(VALID(format)); + + return TrioFormatRef((reference_T *)ref, format, dummy, argarray); +} + + +/************************************************************************* + * + * @SCANNING + * + ************************************************************************/ + + +/************************************************************************* + * TrioSkipWhitespaces [private] + */ +static int +TrioSkipWhitespaces(trio_T *self) +{ + int ch; + + ch = self->current; + while (isspace(ch)) + { + self->InStream(self, &ch); + } + return ch; +} + +/************************************************************************* + * TrioGetCollation [private] + */ +#if TRIO_EXTENSION +static void +TrioGetCollation() +{ + int i; + int j; + int k; + char first[2]; + char second[2]; + + /* This is computational expensive */ + first[1] = NIL; + second[1] = NIL; + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + { + k = 0; + first[0] = (char)i; + for (j = 0; j < MAX_CHARACTER_CLASS; j++) + { + second[0] = (char)j; + if (StrEqualLocale(first, second)) + internalCollationArray[i][k++] = (char)j; + } + internalCollationArray[i][k] = NIL; + } +} +#endif + +/************************************************************************* + * TrioGetCharacterClass [private] + * + * FIXME: + * multibyte + */ +static int +TrioGetCharacterClass(const char *format, + int *indexPointer, + unsigned long *flagsPointer, + int *characterclass) +{ + int index = *indexPointer; + int i; + char ch; + char range_begin; + char range_end; + + *flagsPointer &= ~FLAGS_EXCLUDE; + + if (format[index] == QUALIFIER_CIRCUMFLEX) + { + *flagsPointer |= FLAGS_EXCLUDE; + index++; + } + /* + * If the ungroup character is at the beginning of the scanlist, + * it will be part of the class, and a second ungroup character + * must follow to end the group. + */ + if (format[index] == SPECIFIER_UNGROUP) + { + characterclass[(int)SPECIFIER_UNGROUP]++; + index++; + } + /* + * Minus is used to specify ranges. To include minus in the class, + * it must be at the beginning of the list + */ + if (format[index] == QUALIFIER_MINUS) + { + characterclass[(int)QUALIFIER_MINUS]++; + index++; + } + /* Collect characters */ + for (ch = format[index]; + (ch != SPECIFIER_UNGROUP) && (ch != NIL); + ch = format[++index]) + { + switch (ch) + { + case QUALIFIER_MINUS: /* Scanlist ranges */ + + /* + * Both C99 and UNIX98 describes ranges as implementation- + * defined. + * + * We support the following behaviour (although this may + * change as we become wiser) + * - only increasing ranges, ie. [a-b] but not [b-a] + * - transitive ranges, ie. [a-b-c] == [a-c] + * - trailing minus, ie. [a-] is interpreted as an 'a' + * and a '-' + * - duplicates (although we can easily convert these + * into errors) + */ + range_begin = format[index - 1]; + range_end = format[++index]; + if (range_end == SPECIFIER_UNGROUP) + { + /* Trailing minus is included */ + characterclass[(int)ch]++; + ch = range_end; + break; /* for */ + } + if (range_end == NIL) + return TRIO_ERROR_RETURN(TRIO_EINVAL, index); + if (range_begin > range_end) + return TRIO_ERROR_RETURN(TRIO_ERANGE, index); + + for (i = (int)range_begin; i <= (int)range_end; i++) + characterclass[i]++; + + ch = range_end; + break; + +#if TRIO_EXTENSION + + case SPECIFIER_GROUP: + + switch (format[index + 1]) + { + case QUALIFIER_DOT: /* Collating symbol */ + /* + * FIXME: This will be easier to implement when multibyte + * characters have been implemented. Until now, we ignore + * this feature. + */ + for (i = index + 2; ; i++) + { + if (format[i] == NIL) + /* Error in syntax */ + return -1; + else if (format[i] == QUALIFIER_DOT) + break; /* for */ + } + if (format[++i] != SPECIFIER_UNGROUP) + return -1; + + index = i; + break; + + case QUALIFIER_EQUAL: /* Equivalence class expressions */ + { + unsigned int j; + unsigned int k; + + if (internalCollationUnconverted) + { + /* Lazy evalutation of collation array */ + TrioGetCollation(); + internalCollationUnconverted = FALSE; + } + for (i = index + 2; ; i++) + { + if (format[i] == NIL) + /* Error in syntax */ + return -1; + else if (format[i] == QUALIFIER_EQUAL) + break; /* for */ + else + { + /* Mark any equivalent character */ + k = (unsigned int)format[i]; + for (j = 0; internalCollationArray[k][j] != NIL; j++) + characterclass[(int)internalCollationArray[k][j]]++; + } + } + if (format[++i] != SPECIFIER_UNGROUP) + return -1; + + index = i; + } + break; + + case QUALIFIER_COLON: /* Character class expressions */ + + if (StrEqualMax(CLASS_ALNUM, sizeof(CLASS_ALNUM) - 1, + &format[index])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (isalnum(i)) + characterclass[i]++; + index += sizeof(CLASS_ALNUM) - 1; + } + else if (StrEqualMax(CLASS_ALPHA, sizeof(CLASS_ALPHA) - 1, + &format[index])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (isalpha(i)) + characterclass[i]++; + index += sizeof(CLASS_ALPHA) - 1; + } + else if (StrEqualMax(CLASS_CNTRL, sizeof(CLASS_CNTRL) - 1, + &format[index])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (iscntrl(i)) + characterclass[i]++; + index += sizeof(CLASS_CNTRL) - 1; + } + else if (StrEqualMax(CLASS_DIGIT, sizeof(CLASS_DIGIT) - 1, + &format[index])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (isdigit(i)) + characterclass[i]++; + index += sizeof(CLASS_DIGIT) - 1; + } + else if (StrEqualMax(CLASS_GRAPH, sizeof(CLASS_GRAPH) - 1, + &format[index])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (isgraph(i)) + characterclass[i]++; + index += sizeof(CLASS_GRAPH) - 1; + } + else if (StrEqualMax(CLASS_LOWER, sizeof(CLASS_LOWER) - 1, + &format[index])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (islower(i)) + characterclass[i]++; + index += sizeof(CLASS_LOWER) - 1; + } + else if (StrEqualMax(CLASS_PRINT, sizeof(CLASS_PRINT) - 1, + &format[index])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (isprint(i)) + characterclass[i]++; + index += sizeof(CLASS_PRINT) - 1; + } + else if (StrEqualMax(CLASS_PUNCT, sizeof(CLASS_PUNCT) - 1, + &format[index])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (ispunct(i)) + characterclass[i]++; + index += sizeof(CLASS_PUNCT) - 1; + } + else if (StrEqualMax(CLASS_SPACE, sizeof(CLASS_SPACE) - 1, + &format[index])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (isspace(i)) + characterclass[i]++; + index += sizeof(CLASS_SPACE) - 1; + } + else if (StrEqualMax(CLASS_UPPER, sizeof(CLASS_UPPER) - 1, + &format[index])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (isupper(i)) + characterclass[i]++; + index += sizeof(CLASS_UPPER) - 1; + } + else if (StrEqualMax(CLASS_XDIGIT, sizeof(CLASS_XDIGIT) - 1, + &format[index])) + { + for (i = 0; i < MAX_CHARACTER_CLASS; i++) + if (isxdigit(i)) + characterclass[i]++; + index += sizeof(CLASS_XDIGIT) - 1; + } + else + { + characterclass[(int)ch]++; + } + break; + + default: + characterclass[(int)ch]++; + break; + } + break; + +#endif /* TRIO_EXTENSION */ + + default: + characterclass[(int)ch]++; + break; + } + } + return 0; +} + +/************************************************************************* + * TrioReadNumber [private] + * + * We implement our own number conversion in preference of strtol and + * strtoul, because we must handle 'long long' and thousand separators. + */ +static BOOLEAN_T +TrioReadNumber(trio_T *self, + trio_uintmax_t *target, + unsigned long flags, + int width, + int base) +{ + trio_uintmax_t number = 0; + int digit; + int count; + BOOLEAN_T isNegative = FALSE; + int j; + + assert(VALID(self)); + assert(VALID(self->InStream)); + assert((base >= MIN_BASE && base <= MAX_BASE) || (base == NO_BASE)); + + if (internalDigitsUnconverted) + { + /* Lazy evaluation of digits array */ + memset(internalDigitArray, -1, sizeof(internalDigitArray)); + for (j = 0; j < (int)sizeof(internalDigitsLower) - 1; j++) + { + internalDigitArray[(int)internalDigitsLower[j]] = j; + internalDigitArray[(int)internalDigitsUpper[j]] = j; + } + internalDigitsUnconverted = FALSE; + } + + TrioSkipWhitespaces(self); + + if (!(flags & FLAGS_UNSIGNED)) + { + /* Leading sign */ + if (self->current == '+') + { + self->InStream(self, NULL); + } + else if (self->current == '-') + { + self->InStream(self, NULL); + isNegative = TRUE; + } + } + + count = self->processed; + + if (flags & FLAGS_ALTERNATIVE) + { + switch (base) + { + case NO_BASE: + case BASE_OCTAL: + case BASE_HEX: + case BASE_BINARY: + if (self->current == '0') + { + self->InStream(self, NULL); + if (self->current) + { + if ((base == BASE_HEX) && + (toupper(self->current) == 'X')) + { + self->InStream(self, NULL); + } + else if ((base == BASE_BINARY) && + (toupper(self->current) == 'B')) + { + self->InStream(self, NULL); + } + } + } + else + return FALSE; + break; + default: + break; + } + } + + while (((width == NO_WIDTH) || (self->processed - count < width)) && + (! ((self->current == EOF) || isspace(self->current)))) + { + if (isascii(self->current)) + { + digit = internalDigitArray[self->current]; + /* Abort if digit is not allowed in the specified base */ + if ((digit == -1) || (digit >= base)) + break; + } + else if (flags & FLAGS_QUOTE) + { + /* Compare with thousands separator */ + for (j = 0; internalThousandSeparator[j] && self->current; j++) + { + if (internalThousandSeparator[j] != self->current) + break; + + self->InStream(self, NULL); + } + if (internalThousandSeparator[j]) + break; /* Mismatch */ + else + continue; /* Match */ + } + else + break; + + number *= base; + number += digit; + + self->InStream(self, NULL); + } + + /* Was anything read at all? */ + if (self->processed == count) + return FALSE; + + if (target) + *target = (isNegative) ? -number : number; + return TRUE; +} + +/************************************************************************* + * TrioReadChar [private] + */ +static int +TrioReadChar(trio_T *self, + char *target, + unsigned long flags, + int width) +{ + int i; + char ch; + trio_uintmax_t number; + + assert(VALID(self)); + assert(VALID(self->InStream)); + + for (i = 0; + (self->current != EOF) && (i < width); + i++) + { + ch = (char)self->current; + self->InStream(self, NULL); + if ((flags & FLAGS_ALTERNATIVE) && (ch == CHAR_BACKSLASH)) + { + switch (self->current) + { + case '\\': ch = '\\'; break; + case 'a': ch = '\007'; break; + case 'b': ch = '\b'; break; + case 'f': ch = '\f'; break; + case 'n': ch = '\n'; break; + case 'r': ch = '\r'; break; + case 't': ch = '\t'; break; + case 'v': ch = '\v'; break; + default: + if (isdigit(self->current)) + { + /* Read octal number */ + if (!TrioReadNumber(self, &number, 0, 3, BASE_OCTAL)) + return 0; + ch = (char)number; + } + else if (toupper(self->current) == 'X') + { + /* Read hexadecimal number */ + self->InStream(self, NULL); + if (!TrioReadNumber(self, &number, 0, 2, BASE_HEX)) + return 0; + ch = (char)number; + } + else + { + ch = (char)self->current; + } + break; + } + } + + if (target) + target[i] = ch; + } + return i + 1; +} + +/************************************************************************* + * TrioReadString [private] + */ +static BOOLEAN_T +TrioReadString(trio_T *self, + char *target, + unsigned long flags, + int width) +{ + int i; + + assert(VALID(self)); + assert(VALID(self->InStream)); + + TrioSkipWhitespaces(self); + + /* + * Continue until end of string is reached, a whitespace is encountered, + * or width is exceeded + */ + for (i = 0; + ((width == NO_WIDTH) || (i < width)) && + (! ((self->current == EOF) || isspace(self->current))); + i++) + { + if (TrioReadChar(self, &target[i], flags, 1) == 0) + break; /* for */ + } + if (target) + target[i] = NIL; + return TRUE; +} + +/************************************************************************* + * TrioReadWideChar [private] + */ +#if TRIO_WIDECHAR +static int +TrioReadWideChar(trio_T *self, + wchar_t *target, + unsigned long flags, + int width) +{ + int i; + int j; + int size; + int amount = 0; + wchar_t wch; + char buffer[MB_LEN_MAX + 1]; + + assert(VALID(self)); + assert(VALID(self->InStream)); + + for (i = 0; + (self->current != EOF) && (i < width); + i++) + { + if (isascii(self->current)) + { + if (TrioReadChar(self, buffer, flags, 1) == 0) + return 0; + buffer[1] = NIL; + } + else + { + /* + * Collect a multibyte character, by enlarging buffer until + * it contains a fully legal multibyte character, or the + * buffer is full. + */ + j = 0; + do + { + buffer[j++] = (char)self->current; + buffer[j] = NIL; + self->InStream(self, NULL); + } + while ((j < (int)sizeof(buffer)) && (mblen(buffer, (size_t)j) != j)); + } + if (target) + { + size = mbtowc(&wch, buffer, sizeof(buffer)); + if (size > 0) + target[i] = wch; + } + amount += size; + self->InStream(self, NULL); + } + return amount; +} +#endif /* TRIO_WIDECHAR */ + +/************************************************************************* + * TrioReadWideString [private] + */ +#if TRIO_WIDECHAR +static BOOLEAN_T +TrioReadWideString(trio_T *self, + wchar_t *target, + unsigned long flags, + int width) +{ + int i; + int size; + + assert(VALID(self)); + assert(VALID(self->InStream)); + + TrioSkipWhitespaces(self); + +#if defined(TRIO_COMPILER_SUPPORTS_MULTIBYTE) + mblen(NULL, 0); +#endif + + /* + * Continue until end of string is reached, a whitespace is encountered, + * or width is exceeded + */ + for (i = 0; + ((width == NO_WIDTH) || (i < width)) && + (! ((self->current == EOF) || isspace(self->current))); + ) + { + size = TrioReadWideChar(self, &target[i], flags, 1); + if (size == 0) + break; /* for */ + + i += size; + } + if (target) + target[i] = L'\0'; + return TRUE; +} +#endif /* TRIO_WIDECHAR */ + +/************************************************************************* + * TrioReadGroup [private] + * + * FIXME: characterclass does not work with multibyte characters + */ +static BOOLEAN_T +TrioReadGroup(trio_T *self, + char *target, + int *characterclass, + unsigned long flags, + int width) +{ + int ch; + int i; + + assert(VALID(self)); + assert(VALID(self->InStream)); + + ch = self->current; + for (i = 0; + ((width == NO_WIDTH) || (i < width)) && + (! ((ch == EOF) || + (((flags & FLAGS_EXCLUDE) != 0) ^ (characterclass[ch] == 0)))); + i++) + { + if (target) + target[i] = (char)ch; + self->InStream(self, &ch); + } + + if (target) + target[i] = NIL; + return TRUE; +} + +/************************************************************************* + * TrioReadDouble [private] + * + * FIXME: + * add long double + */ +static BOOLEAN_T +TrioReadDouble(trio_T *self, + double *target, + unsigned long flags, + int width) +{ + int ch; + char doubleString[512] = ""; + int index = 0; + int start; + int j; + BOOLEAN_T isHex = FALSE; + + if ((width == NO_WIDTH) || (width > (int)sizeof(doubleString) - 1)) + width = sizeof(doubleString) - 1; + + TrioSkipWhitespaces(self); + + /* + * Read entire double number from stream. StrToDouble requires a + * string as input, but InStream can be anything, so we have to + * collect all characters. + */ + ch = self->current; + if ((ch == '+') || (ch == '-')) + { + doubleString[index++] = (char)ch; + self->InStream(self, &ch); + width--; + } + + start = index; + switch (ch) + { + case 'n': + case 'N': + /* Not-a-number */ + if (index != 0) + break; + /* FALLTHROUGH */ + case 'i': + case 'I': + /* Infinity */ + while (isalpha(ch) && (index - start < width)) + { + doubleString[index++] = (char)ch; + self->InStream(self, &ch); + } + doubleString[index] = NIL; + + /* Case insensitive string comparison */ + if (StrEqual(&doubleString[start], INFINITE_UPPER) || + StrEqual(&doubleString[start], LONG_INFINITE_UPPER)) + { + *target = ((start == 1 && doubleString[0] == '-')) + ? -HUGE_VAL + : HUGE_VAL; + return TRUE; + } + if (StrEqual(doubleString, NAN_LOWER)) + { + /* NaN must not have a preceeding + nor - */ + *target = TrioGenerateNaN(); + return TRUE; + } + return FALSE; + + default: + break; + } + + if (ch == '0') + { + doubleString[index++] = (char)ch; + self->InStream(self, &ch); + if (toupper(ch) == 'X') + { + isHex = TRUE; + doubleString[index++] = (char)ch; + self->InStream(self, &ch); + } + } + while ((ch != EOF) && (index - start < width)) + { + /* Integer part */ + if (isHex ? isxdigit(ch) : isdigit(ch)) + { + doubleString[index++] = (char)ch; + self->InStream(self, &ch); + } + else if (flags & FLAGS_QUOTE) + { + /* Compare with thousands separator */ + for (j = 0; internalThousandSeparator[j] && self->current; j++) + { + if (internalThousandSeparator[j] != self->current) + break; + + self->InStream(self, &ch); + } + if (internalThousandSeparator[j]) + break; /* Mismatch */ + else + continue; /* Match */ + } + else + break; /* while */ + } + if (ch == '.') + { + /* Decimal part */ + doubleString[index++] = (char)ch; + self->InStream(self, &ch); + while ((isHex ? isxdigit(ch) : isdigit(ch)) && + (index - start < width)) + { + doubleString[index++] = (char)ch; + self->InStream(self, &ch); + } + if (isHex ? (toupper(ch) == 'P') : (toupper(ch) == 'E')) + { + /* Exponent */ + doubleString[index++] = (char)ch; + self->InStream(self, &ch); + if ((ch == '+') || (ch == '-')) + { + doubleString[index++] = (char)ch; + self->InStream(self, &ch); + } + while ((isHex ? isxdigit(ch) : isdigit(ch)) && + (index - start < width)) + { + doubleString[index++] = (char)ch; + self->InStream(self, &ch); + } + } + } + + if ((index == start) || (*doubleString == NIL)) + return FALSE; + + if (flags & FLAGS_LONGDOUBLE) +/* *longdoublePointer = StrToLongDouble()*/ + return FALSE; /* FIXME: Remove when long double is implemented */ + else + { + *target = StrToDouble(doubleString, NULL); + } + return TRUE; +} + +/************************************************************************* + * TrioReadPointer [private] + */ +static BOOLEAN_T +TrioReadPointer(trio_T *self, + void **target, + unsigned long flags) +{ + trio_uintmax_t number; + char buffer[sizeof(null)]; + + flags |= (FLAGS_UNSIGNED | FLAGS_ALTERNATIVE | FLAGS_NILPADDING); + + if (TrioReadNumber(self, + &number, + flags, + POINTER_WIDTH, + BASE_HEX)) + { + /* + * The strange assignment of number is a workaround for a compiler + * warning + */ + if (target) + *target = (char *)0 + number; + return TRUE; + } + else if (TrioReadString(self, + (flags & FLAGS_IGNORE) + ? NULL + : buffer, + 0, + sizeof(null) - 1)) + { + if (StrEqualCase(buffer, null)) + { + if (target) + *target = NULL; + return TRUE; + } + } + return FALSE; +} + +/************************************************************************* + * TrioScan [private] + */ +static int +TrioScan(const void *source, + size_t sourceSize, + void (*InStream)(trio_T *, int *), + const char *format, + va_list arglist, + void **argarray) +{ +#if defined(TRIO_COMPILER_SUPPORTS_MULTIBYTE) + int charlen; +#endif + int status; + int assignment; + parameter_T parameters[MAX_PARAMETERS]; + trio_T internalData; + trio_T *data; + int ch; +#if defined(TRIO_COMPILER_SUPPORTS_MULTIBYTE) + int cnt; +#endif + int index; /* Index of format string */ + int i; /* Index of current parameter */ + unsigned long flags; + int width; + int base; + void *pointer; + + assert(VALID(InStream)); + assert(VALID(format)); + + memset(&internalData, 0, sizeof(internalData)); + data = &internalData; + data->InStream = InStream; + data->location = (void *)source; + data->max = sourceSize; + +#if defined(USE_LOCALE) + if (NULL == internalLocaleValues) + { + TrioSetLocale(); + } +#endif + + status = TrioPreprocess(TYPE_SCAN, format, parameters, arglist, argarray); + if (status < 0) + return status; + + assignment = 0; + i = 0; + index = 0; + data->InStream(data, &ch); + +#if defined(TRIO_COMPILER_SUPPORTS_MULTIBYTE) + mblen(NULL, 0); +#endif + + while (format[index]) + { +#if defined(TRIO_COMPILER_SUPPORTS_MULTIBYTE) + if (! isascii(format[index])) + { + charlen = mblen(&format[index], MB_LEN_MAX); + /* Compare multibyte characters in format string */ + for (cnt = 0; cnt < charlen - 1; cnt++) + { + if (ch != format[index + cnt]) + { + return TRIO_ERROR_RETURN(TRIO_EINVAL, index); + } + data->InStream(data, &ch); + } + continue; /* while */ + } +#endif /* TRIO_COMPILER_SUPPORTS_MULTIBYTE */ + if (EOF == ch) + return EOF; + + if (CHAR_IDENTIFIER == format[index]) + { + if (CHAR_IDENTIFIER == format[index + 1]) + { + /* Two % in format matches one % in input stream */ + if (CHAR_IDENTIFIER == ch) + { + data->InStream(data, &ch); + index += 2; + continue; /* while format chars left */ + } + else + return TRIO_ERROR_RETURN(TRIO_EINVAL, index); + } + + /* Skip the parameter entries */ + while (parameters[i].type == FORMAT_PARAMETER) + i++; + + flags = parameters[i].flags; + /* Find width */ + width = parameters[i].width; + if (flags & FLAGS_WIDTH_PARAMETER) + { + /* Get width from parameter list */ + width = (int)parameters[width].data.number.as_signed; + } + /* Find base */ + base = parameters[i].base; + if (flags & FLAGS_BASE_PARAMETER) + { + /* Get base from parameter list */ + base = (int)parameters[base].data.number.as_signed; + } + + switch (parameters[i].type) + { + case FORMAT_INT: + { + trio_uintmax_t number; + + if (0 == base) + base = BASE_DECIMAL; + + if (!TrioReadNumber(data, + &number, + flags, + width, + base)) + return assignment; + assignment++; + + if (!(flags & FLAGS_IGNORE)) + { + pointer = parameters[i].data.pointer; +#if defined(QUALIFIER_SIZE_T) || defined(QUALIFIER_SIZE_T_UPPER) + if (flags & FLAGS_SIZE_T) + *(size_t *)pointer = (size_t)number; + else +#endif +#if defined(QUALIFIER_PTRDIFF_T) + if (flags & FLAGS_PTRDIFF_T) + *(ptrdiff_t *)pointer = (ptrdiff_t)number; + else +#endif +#if defined(QUALIFIER_INTMAX_T) + if (flags & FLAGS_INTMAX_T) + *(trio_intmax_t *)pointer = (trio_intmax_t)number; + else +#endif + if (flags & FLAGS_QUAD) + *(trio_ulonglong_t *)pointer = (trio_ulonglong_t)number; + else if (flags & FLAGS_LONG) + *(long int *)pointer = (long int)number; + else if (flags & FLAGS_SHORT) + *(short int *)pointer = (short int)number; + else + *(int *)pointer = (int)number; + } + } + break; /* FORMAT_INT */ + + case FORMAT_STRING: +#if TRIO_WIDECHAR + if (flags & FLAGS_WIDECHAR) + { + if (!TrioReadWideString(data, + (flags & FLAGS_IGNORE) + ? NULL + : parameters[i].data.wstring, + flags, + width)) + return assignment; + } + else +#endif + { + if (!TrioReadString(data, + (flags & FLAGS_IGNORE) + ? NULL + : parameters[i].data.string, + flags, + width)) + return assignment; + } + assignment++; + break; /* FORMAT_STRING */ + + case FORMAT_DOUBLE: + if (!TrioReadDouble(data, + (flags & FLAGS_IGNORE) + ? NULL + : parameters[i].data.doublePointer, + flags, + width)) + return assignment; + assignment++; + break; /* FORMAT_DOUBLE */ + + case FORMAT_GROUP: + { + int characterclass[MAX_CHARACTER_CLASS + 1]; + int rc; + + /* Skip over modifiers */ + while (format[index] != SPECIFIER_GROUP) + { + index++; + } + /* Skip over group specifier */ + index++; + + memset(characterclass, 0, sizeof(characterclass)); + rc = TrioGetCharacterClass(format, + &index, + &flags, + characterclass); + if (rc < 0) + return rc; + + if (!TrioReadGroup(data, + (flags & FLAGS_IGNORE) + ? NULL + : parameters[i].data.string, + characterclass, + flags, + parameters[i].width)) + return assignment; + assignment++; + } + break; /* FORMAT_GROUP */ + + case FORMAT_COUNT: + pointer = parameters[i].data.pointer; + if (NULL != pointer) + { +#if defined(QUALIFIER_SIZE_T) || defined(QUALIFIER_SIZE_T_UPPER) + if (flags & FLAGS_SIZE_T) + *(size_t *)pointer = (size_t)data->committed; + else +#endif +#if defined(QUALIFIER_PTRDIFF_T) + if (flags & FLAGS_PTRDIFF_T) + *(ptrdiff_t *)pointer = (ptrdiff_t)data->committed; + else +#endif +#if defined(QUALIFIER_INTMAX_T) + if (flags & FLAGS_INTMAX_T) + *(trio_intmax_t *)pointer = (trio_intmax_t)data->committed; + else +#endif + if (flags & FLAGS_QUAD) + { + *(trio_ulonglong_t *)pointer = (trio_ulonglong_t)data->committed; + } + else if (flags & FLAGS_LONG) + { + *(long int *)pointer = (long int)data->committed; + } + else if (flags & FLAGS_SHORT) + { + *(short int *)pointer = (short int)data->committed; + } + else + { + *(int *)pointer = (int)data->committed; + } + } + break; /* FORMAT_COUNT */ + + case FORMAT_CHAR: +#if TRIO_WIDECHAR + if (flags & FLAGS_WIDECHAR) + { + if (TrioReadWideChar(data, + (flags & FLAGS_IGNORE) + ? NULL + : parameters[i].data.wstring, + flags, + (width == NO_WIDTH) ? 1 : width) > 0) + return assignment; + } + else +#endif + { + if (TrioReadChar(data, + (flags & FLAGS_IGNORE) + ? NULL + : parameters[i].data.string, + flags, + (width == NO_WIDTH) ? 1 : width) > 0) + return assignment; + } + assignment++; + break; /* FORMAT_CHAR */ + + case FORMAT_POINTER: + if (!TrioReadPointer(data, + (flags & FLAGS_IGNORE) + ? NULL + : (void **)parameters[i].data.pointer, + flags)) + return assignment; + assignment++; + break; /* FORMAT_POINTER */ + + case FORMAT_PARAMETER: + break; /* FORMAT_PARAMETER */ + + default: + return TRIO_ERROR_RETURN(TRIO_EINVAL, index); + } + ch = data->current; + index = parameters[i].indexAfterSpecifier; + i++; + } + else /* Not an % identifier */ + { + if (isspace((int)format[index])) + { + /* Whitespaces may match any amount of whitespaces */ + ch = TrioSkipWhitespaces(data); + } + else if (ch == format[index]) + { + data->InStream(data, &ch); + } + else + return TRIO_ERROR_RETURN(TRIO_EINVAL, index); + + index++; + } + } + return assignment; +} + +/************************************************************************* + * TrioInStreamFile [private] + */ +static void +TrioInStreamFile(trio_T *self, + int *intPointer) +{ + FILE *file = (FILE *)self->location; + + assert(VALID(self)); + assert(VALID(file)); + + self->current = fgetc(file); + self->processed++; + self->committed++; + + if (VALID(intPointer)) + { + *intPointer = self->current; + } +} + +/************************************************************************* + * TrioInStreamFileDescriptor [private] + */ +static void +TrioInStreamFileDescriptor(trio_T *self, + int *intPointer) +{ + int fd = *((int *)self->location); + int size; + unsigned char input; + + assert(VALID(self)); + + size = read(fd, &input, sizeof(char)); + self->current = (size == 0) ? EOF : input; + self->processed++; + self->committed++; + + if (VALID(intPointer)) + { + *intPointer = self->current; + } +} + +/************************************************************************* + * TrioInStreamString [private] + */ +static void +TrioInStreamString(trio_T *self, + int *intPointer) +{ + unsigned char **buffer; + + assert(VALID(self)); + assert(VALID(self->InStream)); + assert(VALID(self->location)); + + buffer = (unsigned char **)self->location; + self->current = (*buffer)[0]; + if (self->current == NIL) + self->current = EOF; + (*buffer)++; + self->processed++; + self->committed++; + + if (VALID(intPointer)) + { + *intPointer = self->current; + } +} + +/************************************************************************* + * scanf + */ +int +trio_scanf(const char *format, + ...) +{ + int status; + va_list args; + + assert(VALID(format)); + + va_start(args, format); + status = TrioScan(stdin, 0, TrioInStreamFile, format, args, NULL); + va_end(args); + return status; +} + +int +trio_vscanf(const char *format, + va_list args) +{ + assert(VALID(format)); + + return TrioScan(stdin, 0, TrioInStreamFile, format, args, NULL); +} + +int +trio_scanfv(const char *format, + void **args) +{ + va_list dummy; + shutup_unitialized(&dummy); + + assert(VALID(format)); + + return TrioScan(stdin, 0, TrioInStreamFile, format, dummy, args); +} + +/************************************************************************* + * fscanf + */ +int +trio_fscanf(FILE *file, + const char *format, + ...) +{ + int status; + va_list args; + + assert(VALID(file)); + assert(VALID(format)); + + va_start(args, format); + status = TrioScan(file, 0, TrioInStreamFile, format, args, NULL); + va_end(args); + return status; +} + +int +trio_vfscanf(FILE *file, + const char *format, + va_list args) +{ + assert(VALID(file)); + assert(VALID(format)); + + return TrioScan(file, 0, TrioInStreamFile, format, args, NULL); +} + +int +trio_fscanfv(FILE *file, + const char *format, + void **args) +{ + va_list dummy; + shutup_unitialized(&dummy); + + assert(VALID(file)); + assert(VALID(format)); + + return TrioScan(file, 0, TrioInStreamFile, format, dummy, args); +} + +/************************************************************************* + * dscanf + */ +int +trio_dscanf(int fd, + const char *format, + ...) +{ + int status; + va_list args; + + assert(VALID(format)); + + va_start(args, format); + status = TrioScan(&fd, 0, TrioInStreamFileDescriptor, format, args, NULL); + va_end(args); + return status; +} + +int +trio_vdscanf(int fd, + const char *format, + va_list args) +{ + assert(VALID(format)); + + return TrioScan(&fd, 0, TrioInStreamFileDescriptor, format, args, NULL); +} + +int +trio_dscanfv(int fd, + const char *format, + void **args) +{ + va_list dummy; + shutup_unitialized(&dummy); + + assert(VALID(format)); + + return TrioScan(&fd, 0, TrioInStreamFileDescriptor, format, dummy, args); +} + +/************************************************************************* + * sscanf + */ +int +trio_sscanf(const char *buffer, + const char *format, + ...) +{ + int status; + va_list args; + + assert(VALID(buffer)); + assert(VALID(format)); + + va_start(args, format); + status = TrioScan(&buffer, 0, TrioInStreamString, format, args, NULL); + va_end(args); + return status; +} + +int +trio_vsscanf(const char *buffer, + const char *format, + va_list args) +{ + assert(VALID(buffer)); + assert(VALID(format)); + + return TrioScan(&buffer, 0, TrioInStreamString, format, args, NULL); +} + +int +trio_sscanfv(const char *buffer, + const char *format, + void **args) +{ + va_list dummy; + shutup_unitialized(&dummy); + + assert(VALID(buffer)); + assert(VALID(format)); + + return TrioScan(&buffer, 0, TrioInStreamString, format, dummy, args); +} + diff --git a/org.glite.lbjp-common.trio/src/triop.h b/org.glite.lbjp-common.trio/src/triop.h new file mode 100644 index 0000000..ca49fab --- /dev/null +++ b/org.glite.lbjp-common.trio/src/triop.h @@ -0,0 +1,138 @@ +/************************************************************************* + * + * $Id$ + * + * Copyright (C) 2000 Bjorn Reese and Daniel Stenberg. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND + * CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER. + * + ************************************************************************ + * + * Private functions, types, etc. used for callback functions. + * + * The ref pointer is an opaque type and should remain as such. + * Private data must only be accessible through the getter and + * setter functions. + * + ************************************************************************/ + +#ifndef TRIO_TRIOP_H +#define TRIO_TRIOP_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef TRIO_C99 +# define TRIO_C99 1 +#endif +#ifndef TRIO_BSD +# define TRIO_BSD 1 +#endif +#ifndef TRIO_GNU +# define TRIO_GNU 1 +#endif +#ifndef TRIO_MISC +# define TRIO_MISC 1 +#endif +#ifndef TRIO_UNIX98 +# define TRIO_UNIX98 1 +#endif +#ifndef TRIO_MICROSOFT +# define TRIO_MICROSOFT 1 +#endif +#ifndef TRIO_EXTENSION +# define TRIO_EXTENSION 1 +#endif +#ifndef TRIO_WIDECHAR +# define TRIO_WIDECHAR 0 +#endif +#ifndef TRIO_ERRORS +# define TRIO_ERRORS 1 +#endif + +#ifndef TRIO_MALLOC +# define TRIO_MALLOC(n) malloc(n) +#endif +#ifndef TRIO_REALLOC +# define TRIO_REALLOC(x,n) realloc((x),(n)) +#endif +#ifndef TRIO_FREE +# define TRIO_FREE(x) free(x) +#endif + +typedef int (*trio_callback_t)(void *ref); + +void *trio_register(trio_callback_t callback, const char *name); +void trio_unregister(void *handle); + +const char *trio_get_format(void *ref); +void *trio_get_argument(void *ref); + +/* Modifiers */ +int trio_get_width(void *ref); +void trio_set_width(void *ref, int width); +int trio_get_precision(void *ref); +void trio_set_precision(void *ref, int precision); +int trio_get_base(void *ref); +void trio_set_base(void *ref, int base); +int trio_get_padding(void *ref); +void trio_set_padding(void *ref, int is_padding); +int trio_get_short(void *ref); /* h */ +void trio_set_shortshort(void *ref, int is_shortshort); +int trio_get_shortshort(void *ref); /* hh */ +void trio_set_short(void *ref, int is_short); +int trio_get_long(void *ref); /* l */ +void trio_set_long(void *ref, int is_long); +int trio_get_longlong(void *ref); /* ll */ +void trio_set_longlong(void *ref, int is_longlong); +int trio_get_longdouble(void *ref); /* L */ +void trio_set_longdouble(void *ref, int is_longdouble); +int trio_get_alternative(void *ref); /* # */ +void trio_set_alternative(void *ref, int is_alternative); +int trio_get_alignment(void *ref); /* - */ +void trio_set_alignment(void *ref, int is_leftaligned); +int trio_get_spacing(void *ref); /* (space) */ +void trio_set_spacing(void *ref, int is_space); +int trio_get_sign(void *ref); /* + */ +void trio_set_sign(void *ref, int is_showsign); +int trio_get_quote(void *ref); /* ' */ +void trio_set_quote(void *ref, int is_quote); +int trio_get_upper(void *ref); +void trio_set_upper(void *ref, int is_upper); +#if TRIO_C99 +int trio_get_largest(void *ref); /* j */ +void trio_set_largest(void *ref, int is_largest); +int trio_get_ptrdiff(void *ref); /* t */ +void trio_set_ptrdiff(void *ref, int is_ptrdiff); +int trio_get_size(void *ref); /* z / Z */ +void trio_set_size(void *ref, int is_size); +#endif + +/* Printing */ +int trio_print_ref(void *ref, const char *format, ...); +int trio_vprint_ref(void *ref, const char *format, va_list args); +int trio_printv_ref(void *ref, const char *format, void **args); + +void trio_print_int(void *ref, int number); +void trio_print_uint(void *ref, unsigned int number); +/* void trio_print_long(void *ref, long number); */ +/* void trio_print_ulong(void *ref, unsigned long number); */ +void trio_print_double(void *ref, double number); +void trio_print_string(void *ref, char *string); +void trio_print_pointer(void *ref, void *pointer); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* TRIO_TRIOP_H */ diff --git a/org.glite.lbjp-common.trio/test/trio_test.cpp b/org.glite.lbjp-common.trio/test/trio_test.cpp new file mode 100644 index 0000000..3ba4b9c --- /dev/null +++ b/org.glite.lbjp-common.trio/test/trio_test.cpp @@ -0,0 +1,85 @@ +#include + +#include +#include + +#include "trio.h" + +class TrioTest: public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TrioTest); + CPPUNIT_TEST(escapeULM); + CPPUNIT_TEST(escapeXML); + CPPUNIT_TEST(escapeSQL); + CPPUNIT_TEST_SUITE_END(); + +public: + void escapeULM(); + void escapeXML(); + void escapeSQL(); +}; + +void TrioTest::escapeULM() +{ + char *e, *r = "START we have =, \\\", and \\n in the string END"; + + trio_asprintf(&e,"START %|Us END", "we have =, \", and \n in the string"), + std::cerr << e << std::endl; + + CPPUNIT_ASSERT_MESSAGE("escape ULM failed",!strcmp(e,r)); +} + +void TrioTest::escapeXML() +{ + char *e, *r = "START there is a <tag> containing &something; </tag> END"; + + trio_asprintf(&e,"START %|Xs END", "there is a containing &something; "), + std::cerr << e << std::endl; + + CPPUNIT_ASSERT_MESSAGE("escape XML failed",!strcmp(e,r)); +} + +void TrioTest::escapeSQL() +{ + char *e, *r = "START SQL doesn''t like '' END"; + + trio_asprintf(&e,"START %|Ss END", "SQL doesn't like '"), + std::cerr << e << std::endl; + + CPPUNIT_ASSERT_MESSAGE("escape SQL failed",!strcmp(e,r)); +} + +CPPUNIT_TEST_SUITE_REGISTRATION( TrioTest ); + +#include +#include + +#include +#include +#include +#include +#include +#include + +int main (int argc,const char *argv[]) +{ + CppUnit::Test *suite = CppUnit::TestFactoryRegistry::getRegistry().makeTest(); + + assert(argc == 2); + std::ofstream xml(argv[1]); + + CppUnit::TestResult controller; + CppUnit::TestResultCollector result; + controller.addListener( &result ); + + CppUnit::TestRunner runner; + runner.addTest(suite); + runner.run(controller); + + CppUnit::XmlOutputter xout( &result, xml ); + CppUnit::CompilerOutputter tout( &result, std::cout); + xout.write(); + tout.write(); + + return result.wasSuccessful() ? 0 : 1 ; +} -- 1.8.2.3