--- /dev/null
+build
+doc
+reports
--- /dev/null
+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
+<email address>.
+
+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/
+
+
--- /dev/null
+# 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
--- /dev/null
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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;
+}
--- /dev/null
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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;
+}
--- /dev/null
+#ifndef GLITE_LBU_DB_H
+#define GLITE_LBU_DB_H
+
+#ident "$Header$"
+
+
+#include <time.h>
+
+
+#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
--- /dev/null
+#ident "$Header$"
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <time.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <mysql.h>
+#include <mysqld_error.h>
+#include <errmsg.h>
+
+#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<ret; i++)
+ if (!strcasecmp(col_names[i],"Key_name")) Key_name = i;
+ else if (!strcasecmp(col_names[i],"Seq_in_index")) Seq_in_index = i;
+ else if (!strcasecmp(col_names[i],"Column_name")) Column_name = i;
+ else if (!strcasecmp(col_names[i],"Sub_part")) Sub_part = i;
+
+ assert(Key_name >= 0 && Seq_in_index >= 0 &&
+ Column_name >= 0 && Sub_part >= 0);
+
+ }
+
+ for (i=0; i<nkeys && strcasecmp(showcol[Key_name],keys[i]); i++);
+
+ if (i == nkeys) {
+ keys = realloc(keys,(i+2) * sizeof keys[0]);
+ keys[i] = showcol[Key_name];
+//printf("** KEY [%d] %s\n", i, keys[i]);
+ keys[i+1] = NULL;
+ cols = realloc(cols,(i+1) * sizeof cols[0]);
+ cols[i] = 0;
+ idx = realloc(idx,(i+2) * sizeof idx[0]);
+ idx[i] = idx[i+1] = NULL;
+ showcol[Key_name] = NULL;
+ nkeys++;
+ }
+
+ j = atoi(showcol[Seq_in_index])-1;
+ if (cols[i] <= j) {
+ cols[i] = j+1;
+ idx[i] = realloc(idx[i],(j+2)*sizeof idx[i][0]);
+ memset(&idx[i][j+1],0,sizeof idx[i][0]);
+ }
+ idx[i][j] = strdup(showcol[Column_name]);
+//printf("****** [%d, %d] %s\n", i, j, idx[i][j]);
+//FIXME: needed?idx[i][j].value.i = atoi(showcol[Sub_part]);
+ for (i = 0; showcol[i]; i++) free(showcol[i]);
+ }
+
+ glite_lbu_FreeStmt(&stmt);
+ free(cols);
+ free(col_names);
+
+ if (ret == 0) CLR_ERR(ctx);
+ else {
+ free(keys);
+ keys = NULL;
+ for (i = 0; idx[i]; i++) {
+ for (j = 0; idx[i][j]; j++) free(idx[i][j]);
+ free(idx[i]);
+ }
+ free(idx);
+ idx = NULL;
+ }
+
+ if (key_names) *key_names = keys;
+ else free(keys);
+ *column_names = idx;
+
+ return STATUS(ctx);
+}
+
+
+/* ---- simple ---- */
+
+int glite_lbu_ExecSQL(glite_lbu_DBContext ctx, const char *cmd, glite_lbu_Statement *stmt) {
+ int merr;
+ int retry_nr = 0;
+ int do_reconnect = 0;
+#ifdef LBS_DB_PROFILE
+ struct timeval start,end;
+ int pid;
+
+ static struct timeval sum = {
+ tv_sec: 0,
+ tv_usec: 0
+ };
+#endif
+
+ CLR_ERR(ctx);
+
+ if (stmt) *stmt = NULL;
+
+#ifdef LBS_DB_PROFILE
+ gettimeofday(&start,NULL);
+#endif
+
+ while (retry_nr == 0 || do_reconnect) {
+ do_reconnect = 0;
+ if (mysql_query(ctx->mysql, 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<pid>
+ * 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; i<nr; i++) {
+ if (lengths) lengths[i] = len[i];
+ if (len[i]) {
+ results[i] = malloc(len[i] + 1);
+ memcpy(results[i], row[i], len[i]);
+ results[i][len[i]] = '\000';
+ } else
+ results[i] = strdup("");
+ }
+
+ return nr;
+}
+
+
+/*
+ * prepared version of the fetch
+ */
+static int FetchRowPrepared(glite_lbu_DBContext ctx, glite_lbu_Statement stmt, unsigned int n, unsigned long *lengths, char **results) {
+ int ret, retry, i;
+ MYSQL_BIND *binds = NULL;
+ unsigned long *lens = NULL;
+
+ if (n != stmt->nrfields) {
+ 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);
+}
--- /dev/null
+# 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 $<
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#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 <text> message to send\n"
+ " -p, --port <num> 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;
+}
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#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;
+}
--- /dev/null
+#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__ */
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <netdb.h>
+#include <assert.h>
+#include <syslog.h>
+#include <sys/time.h>
+#include <time.h>
+#include <stdarg.h>
+#include <signal.h>
+
+#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;
+}
--- /dev/null
+build
\ No newline at end of file
--- /dev/null
+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
+<email address>.
+
+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/
+
+
--- /dev/null
+# 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 $<
--- /dev/null
+#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__ */
--- /dev/null
+/*************************************************************************
+ *
+ * $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 <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+#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 <stdio.h> 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 */
--- /dev/null
+#ident "$Header$"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#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<size; i++) {
+ if ((str[i] != ULM_BS) && (str[i] != ULM_QM) && (str[i] != ULM_LF)) {
+ ret[j] = str[i];
+ j++;
+ }
+ else {
+ ret[j] = ULM_BS;
+ if (str[i] == ULM_LF) {
+ ret[j+1] = 'n';
+ }
+ else {
+ ret[j+1] = str[i];
+ }
+ j += 2;
+ }
+} /* for */
+
+ret[j] = 0;
+
+return ret;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * \fn char *glite_lbu_UnescapeULM(const char *str)
+ * \param str a string to unescape
+ * \return new (allocated) unescaped string
+ * \brief in given string unescape all escaped ULM_QM, ULM_BS and ULM_LF
+ *
+ * Calls: malloc, strlen
+ *
+ * Algorithm: array lookup
+ * - the new string will be allocated
+ *
+ *----------------------------------------------------------------------
+ */
+
+char *glite_lbu_UnescapeULM(const char *str)
+{
+unsigned int i,j;
+size_t size;
+char *ret;
+
+if (str == NULL) return NULL;
+
+size = strlen(str);
+ret = (char*) malloc(1+size*sizeof(char));
+
+/*
+j = 0;
+for (i=0; i<size; i++) {
+ if ( (str[i] != ULM_BS) ||
+ ((str[i] == ULM_BS) && ((str[i+1] != ULM_BS) && (str[i+1] != ULM_QM) && (str[i+1] != 'n'))) )
+ {
+ if (str[i] == ULM_LF) { ret[j] = 'n'; }
+ else { ret[j] = str[i]; }
+ j++;
+ }
+}
+*/
+for (i=j=0; i<size; i++,j++)
+ if (str[i] == ULM_BS) switch(str[++i]) {
+ case 'n': ret[j] = ULM_LF; break;
+ default: ret[j] = str[i]; break;
+ } else { ret[j] = str[i]; }
+
+ret[j] = '\0';
+
+return ret;
+}
+
+static const struct {
+ const char c,*e;
+} xml_etab[] = {
+ { '<',"lt" },
+ { '>',"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;
+}
--- /dev/null
+/*************************************************************************
+ *
+ * $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 <string.h>
+#include <locale.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <time.h>
+#include <math.h>
+#ifndef DEBUG
+# define NDEBUG
+#endif
+#include <assert.h>
+
+#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 ::= [ <sign> ]
+ * ( <number> |
+ * <number> <decimal_point> <number> |
+ * <decimal_point> <number> )
+ * [ <exponential> [ <sign> ] <number> ]
+ * number ::= 1*( <digit> )
+ * 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;
+}
--- /dev/null
+/*************************************************************************
+ *
+ * $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 <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#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 */
--- /dev/null
+
+/*************************************************************************
+ *
+ * $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 <alloc> 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 <assert.h>
+#include <ctype.h>
+#if !defined(TRIO_COMPILER_SUPPORTS_C99) && !defined(isblank)
+# define isblank(x) (((x)==32) || ((x)==9))
+#endif
+#include <math.h>
+#include <limits.h>
+#include <float.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <errno.h>
+
+#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 <unistd.h>
+# include <signal.h>
+# include <locale.h>
+# define USE_LOCALE
+#endif /* PLATFORM_UNIX */
+#if defined(PLATFORM_WIN32)
+# include <io.h>
+# define read _read
+# define write _write
+#endif /* PLATFORM_WIN32 */
+
+#if TRIO_WIDECHAR
+# if defined(TRIO_COMPILER_SUPPORTS_ISO94)
+# include <wchar.h>
+# include <wctype.h>
+# 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 <stdint.h>
+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 <inttypes.h>
+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<bits> 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);
+}
+
--- /dev/null
+/*************************************************************************
+ *
+ * $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 <stdlib.h>
+
+#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 */
--- /dev/null
+#include <iostream>
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/TestFixture.h>
+
+#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 <tag> containing &something; </tag>"),
+ 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 <assert.h>
+#include <fstream>
+
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <cppunit/CompilerOutputter.h>
+#include <cppunit/XmlOutputter.h>
+#include <cppunit/TestRunner.h>
+#include <cppunit/TestResult.h>
+#include <cppunit/TestResultCollector.h>
+
+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 ;
+}