Index: /trunk/dbconfig/ipp.m4
===================================================================
--- /trunk/dbconfig/ipp.m4	(revision 23879)
+++ /trunk/dbconfig/ipp.m4	(revision 23880)
@@ -25,2 +25,3 @@
 include(dist.md)
 include(rc.md)
+include(receive.md)
Index: /trunk/dbconfig/receive.md
===================================================================
--- /trunk/dbconfig/receive.md	(revision 23880)
+++ /trunk/dbconfig/receive.md	(revision 23880)
@@ -0,0 +1,24 @@
+# Tables for receiving files
+
+receiveSource	METADATA
+	source_id	S64	0	# Primary Key AUTO_INCREMENT
+	source		STR	127	# Key
+	product		STR	64	# Key
+	workdir		STR	255
+	comment		STR	255	# Key
+	fileset_last	STR	128
+END
+
+receiveRun	METADATA
+	receive_id	S64	0	# Primary Key AUTO_INCREMENT
+	source_id	S64	0	# Key fkey (source_id) ref receiveSource(source_id)
+	fileset		STR	128
+END
+
+receiveResult	METADATA
+	receive_id	S64	0	# Primary Key fkey (receive_id) ref receiveRun(receive_id)
+	dtime_copy	F32	0.0
+	dtime_extract	F32	0.0
+	fault		S32	0	# Key
+END
+
Index: /trunk/ippTools/share/pxadmin_create_tables.sql
===================================================================
--- /trunk/ippTools/share/pxadmin_create_tables.sql	(revision 23879)
+++ /trunk/ippTools/share/pxadmin_create_tables.sql	(revision 23880)
@@ -1303,4 +1303,38 @@
 )  ENGINE=innodb DEFAULT CHARSET=latin1;
 
+-- Sources from which to receive files
+CREATE TABLE receiveSource (
+    source_id BIGINT AUTO_INCREMENT, -- unique identifier
+    source VARCHAR(128) NOT NULL, -- source URI
+    product VARCHAR(64) NOT NULL, -- product of interest
+    workdir VARCHAR(255),       -- where to extract (optional)
+    comment VARCHAR(255),       -- for human memory
+    fileset_last VARCHAR(128),  -- last fileset seen
+    PRIMARY KEY(source_id),
+    KEY(source),
+    KEY(product),
+    KEY(comment)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+-- Run for receiving files
+CREATE TABLE receiveRun (
+    receive_id BIGINT AUTO_INCREMENT, -- unique identifier
+    source_id BIGINT NOT NULL,  -- link to receiveSource
+    fileset VARCHAR(128) NOT NULL, -- fileset to receive
+    PRIMARY KEY(receive_id),
+    KEY(source_id),
+    FOREIGN KEY(source_id) REFERENCES receiveSource(source_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+-- Result of receiving files
+CREATE TABLE receiveResult (
+    receive_id BIGINT AUTO_INCREMENT, -- unique identifier, link to receiveRun
+    dtime_copy FLOAT,           -- Time to copy
+    dtime_extract FLOAT,        -- Time to extract
+    fault SMALLINT NOT NULL DEFAULT 0, -- Fault code
+    PRIMARY KEY(receive_id),
+    KEY(fault),
+    FOREIGN KEY(receive_id) REFERENCES receiveRun(receive_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
 
 
Index: /trunk/ippTools/share/receivetool_list.sql
===================================================================
--- /trunk/ippTools/share/receivetool_list.sql	(revision 23880)
+++ /trunk/ippTools/share/receivetool_list.sql	(revision 23880)
@@ -0,0 +1,2 @@
+SELECT *
+FROM receiveSource
Index: /trunk/ippTools/share/receivetool_pendingrun.sql
===================================================================
--- /trunk/ippTools/share/receivetool_pendingrun.sql	(revision 23880)
+++ /trunk/ippTools/share/receivetool_pendingrun.sql	(revision 23880)
@@ -0,0 +1,5 @@
+SELECT *
+FROM receiveRun
+JOIN receiveSource USING(source_id)
+LEFT JOIN receiveResult USING(receive_id)
+WHERE receiveResult.receive_id IS NULL
Index: /trunk/ippTools/share/receivetool_revert.sql
===================================================================
--- /trunk/ippTools/share/receivetool_revert.sql	(revision 23880)
+++ /trunk/ippTools/share/receivetool_revert.sql	(revision 23880)
@@ -0,0 +1,5 @@
+DELETE FROM receiveResult
+USING receiveResult, receiveRun, receiveSource
+WHERE receiveResult.receive_id = receiveRun.receive_id
+    AND receiveRun.source_id = receiveSource.source_id
+    AND fault != 0
Index: /trunk/ippTools/src/Makefile.am
===================================================================
--- /trunk/ippTools/src/Makefile.am	(revision 23879)
+++ /trunk/ippTools/src/Makefile.am	(revision 23880)
@@ -19,5 +19,6 @@
 	regtool \
 	stacktool \
-	warptool
+	warptool \
+	receivetool
 
 
@@ -212,4 +213,11 @@
     pxinjectConfig.c
 
+
+receivetool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+receivetool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+receivetool_SOURCES = \
+    receivetool.c \
+    receivetoolConfig.c
+
 clean-local:
 	-rm -f TAGS
Index: /trunk/ippTools/src/receivetool.c
===================================================================
--- /trunk/ippTools/src/receivetool.c	(revision 23880)
+++ /trunk/ippTools/src/receivetool.c	(revision 23880)
@@ -0,0 +1,306 @@
+/*
+ * disttool.c
+ *
+ * Copyright (C) 2008
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include "pxtools.h"
+#include "pxdata.h"
+#include "receivetool.h"
+
+static bool definesourceMode(pxConfig *config);
+static bool listMode(pxConfig *config);
+static bool definerunMode(pxConfig *config);
+static bool updatelastMode(pxConfig *config);
+static bool pendingrunMode(pxConfig *config);
+static bool addresultMode(pxConfig *config);
+static bool revertMode(pxConfig *config);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = receivetoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(RECEIVETOOL_MODE_DEFINESOURCE, definesourceMode);
+        MODECASE(RECEIVETOOL_MODE_LIST, listMode);
+        MODECASE(RECEIVETOOL_MODE_DEFINERUN, definerunMode);
+        MODECASE(RECEIVETOOL_MODE_UPDATELAST, updatelastMode);
+        MODECASE(RECEIVETOOL_MODE_PENDINGRUN, pendingrunMode);
+        MODECASE(RECEIVETOOL_MODE_ADDRESULT, addresultMode);
+        MODECASE(RECEIVETOOL_MODE_REVERT, revertMode);
+      default:
+        psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+static bool definesourceMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_STR(source, config->args, "-stage", true, false);
+    PXOPT_LOOKUP_STR(product, config->args, "-product",  true, false);
+
+    // optional
+    PXOPT_LOOKUP_STR(workdir, config->args, "-workdir", false, false);
+    PXOPT_LOOKUP_STR(comment, config->args, "-comment",  false, false);
+    PXOPT_LOOKUP_STR(last, config->args, "-last",  false, false);
+
+    if (!receiveSourceInsert(config->dbh, 0, source, product, workdir, comment, last)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool listMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc(); // WHERE conditions
+
+    // required
+    PXOPT_COPY_STR(config->args, where, "-source", "receiveSource.source", "==");
+    PXOPT_COPY_STR(config->args, where, "-product", "receiveSource.product", "==");
+    PXOPT_COPY_S64(config->args, where, "-comment", "receiveSource.comment", "LIKE");
+
+    psString query = pxDataGet("receivetool_list.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "Failed to retreive SQL statement");
+        psFree(where);
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " WHERE %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQueryF(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "Database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "Database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("receivetool", PS_LOG_INFO, "No rows found");
+        psFree(output);
+        return true;
+    }
+    if (!ippdbPrintMetadatas(stdout, output, "receiveSource", true)) {
+        psError(PS_ERR_UNKNOWN, false, "Failed to print array");
+        psFree(output);
+        return false;
+    }
+    psFree(output);
+
+    return true;
+}
+
+static bool definerunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_S64(source_id, config->args, "-source_id", true, false);
+    PXOPT_LOOKUP_STR(fileset, config->args, "-fileset",  true, false);
+
+    if (!receiveRunInsert(config->dbh, 0, source_id, fileset)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool updatelastMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_S64(source_id, config->args, "-source_id", true, false);
+    PXOPT_LOOKUP_STR(fileset, config->args, "-fileset",  true, false);
+
+    psString query = NULL;              // Query to execute
+    psStringAppend(&query, "UPDATE receiveSource SET fileset_last = \'%s\' WHERE source_id = %" PRId64,
+                   fileset, source_id);
+
+    if (!p_psDBRunQueryF(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "Database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    return true;
+}
+
+
+static bool pendingrunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc(); // WHERE conditions
+
+    // required
+    PXOPT_COPY_STR(config->args, where, "-source", "receiveSource.source", "==");
+    PXOPT_COPY_STR(config->args, where, "-product", "receiveSource.product", "==");
+    PXOPT_COPY_S64(config->args, where, "-comment", "receiveSource.comment", "LIKE");
+
+    psString query = pxDataGet("receivetool_pendingrun.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "Failed to retreive SQL statement");
+        psFree(where);
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQueryF(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "Database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "Database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("receivetool", PS_LOG_INFO, "No rows found");
+        psFree(output);
+        return true;
+    }
+    if (!ippdbPrintMetadatas(stdout, output, "receiveRun", true)) {
+        psError(PS_ERR_UNKNOWN, false, "Failed to print array");
+        psFree(output);
+        return false;
+    }
+    psFree(output);
+
+    return true;
+}
+
+static bool addresultMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_S64(receive_id, config->args, "-source_id", true, false);
+
+    // optional
+    PXOPT_LOOKUP_F32(dtime_copy, config->args, "-dtime_copy", false, false);
+    PXOPT_LOOKUP_F32(dtime_extract, config->args, "-dtime_extract", false, false);
+    PXOPT_LOOKUP_S32(fault, config->args, "-fault", false, false);
+
+    if (!receiveResultInsert(config->dbh, receive_id, dtime_copy, dtime_extract, fault)) {
+        psError(PS_ERR_UNKNOWN, false, "Database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool revertMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc(); // WHERE conditions
+
+    PXOPT_COPY_S64(config->args, where, "-receive_id", "receiveResult.receive_id", "==");
+    PXOPT_COPY_S32(config->args, where, "-fault", "receiveResult.fault", "==");
+    PXOPT_COPY_STR(config->args, where, "-source", "receiveSource.source", "==");
+    PXOPT_COPY_STR(config->args, where, "-product", "receiveSource.product", "==");
+    PXOPT_COPY_STR(config->args, where, "-comment", "receiveSource.comment", "LIKE");
+
+    psString query = pxDataGet("receivetool_revert.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "Failed to retreive SQL statement");
+        psFree(where);
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " WHERE %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQueryF(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "Database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    return true;
+}
Index: /trunk/ippTools/src/receivetool.h
===================================================================
--- /trunk/ippTools/src/receivetool.h	(revision 23880)
+++ /trunk/ippTools/src/receivetool.h	(revision 23880)
@@ -0,0 +1,38 @@
+/*
+ * receivetool.h
+ *
+ * Copyright (C) 2008 IfA
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef RECEIVETOOL_H
+#define RECEIVETOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    RECEIVETOOL_MODE_NONE      = 0x0,
+    RECEIVETOOL_MODE_DEFINESOURCE,
+    RECEIVETOOL_MODE_LIST,
+    RECEIVETOOL_MODE_DEFINERUN,
+    RECEIVETOOL_MODE_UPDATELAST,
+    RECEIVETOOL_MODE_PENDINGRUN,
+    RECEIVETOOL_MODE_ADDRESULT,
+    RECEIVETOOL_MODE_REVERT,
+} receivetoolMode;
+
+pxConfig *receivetoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // RECEIVETOOL_H
Index: /trunk/ippTools/src/receivetoolConfig.c
===================================================================
--- /trunk/ippTools/src/receivetoolConfig.c	(revision 23880)
+++ /trunk/ippTools/src/receivetoolConfig.c	(revision 23880)
@@ -0,0 +1,122 @@
+/*
+ * receivetoolConfig.c
+ *
+ * Copyright (C) 2008
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "receivetool.h"
+
+pxConfig *receivetoolConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration!");
+        psFree(config);
+        return NULL;
+    }
+
+    // -definesource
+    psMetadata *definesourceArgs = psMetadataAlloc();
+    psMetadataAddStr(definesourceArgs, PS_LIST_TAIL, "-source", 0, "define source (required)", NULL);
+    psMetadataAddStr(definesourceArgs, PS_LIST_TAIL, "-product", 0, "define product (required)", NULL);
+    psMetadataAddStr(definesourceArgs, PS_LIST_TAIL, "-workdir",   0, "define workdir", NULL);
+    psMetadataAddStr(definesourceArgs, PS_LIST_TAIL, "-comment", 0, "define comment", NULL);
+    psMetadataAddStr(definesourceArgs, PS_LIST_TAIL, "-last", 0, "define last fileset", NULL);
+
+    // -list
+    psMetadata *listArgs = psMetadataAlloc();
+    psMetadataAddStr(listArgs, PS_LIST_TAIL, "-source", 0, "search on source", NULL);
+    psMetadataAddStr(listArgs, PS_LIST_TAIL, "-product", 0, "search on product", NULL);
+    psMetadataAddStr(listArgs, PS_LIST_TAIL, "-comment", 0, "search on comment (LIKE)", NULL);
+
+    // -definerun
+    psMetadata *definerunArgs = psMetadataAlloc();
+    psMetadataAddS64(definerunArgs, PS_LIST_TAIL, "-source_id", 0, "define source_id (required)", 0);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-fileset", 0, "define fileset (required)", NULL);
+
+    // -updatelast
+    psMetadata *updatelastArgs = psMetadataAlloc();
+    psMetadataAddS64(updatelastArgs, PS_LIST_TAIL, "-source_id", 0, "define source_id (required)", 0);
+    psMetadataAddStr(updatelastArgs, PS_LIST_TAIL, "-fileset", 0, "define last fileset (required)", NULL);
+
+    // -pendingrun
+    psMetadata *pendingrunArgs = psMetadataAlloc();
+    psMetadataAddStr(pendingrunArgs, PS_LIST_TAIL, "-source", 0, "search on source", NULL);
+    psMetadataAddStr(pendingrunArgs, PS_LIST_TAIL, "-product", 0, "search on product", NULL);
+    psMetadataAddStr(pendingrunArgs, PS_LIST_TAIL, "-comment", 0, "search on comment (LIKE)", NULL);
+
+    // -addresult
+    psMetadata *addresultArgs = psMetadataAlloc();
+    psMetadataAddS64(addresultArgs, PS_LIST_TAIL, "-receive_id", 0, "define receive_id (required)", 0);
+    psMetadataAddF32(addresultArgs, PS_LIST_TAIL, "-dtime_copy", 0, "define time to copy", NAN);
+    psMetadataAddF32(addresultArgs, PS_LIST_TAIL, "-dtime_extract", 0, "define time to extract", NAN);
+    psMetadataAddS32(addresultArgs, PS_LIST_TAIL, "-fault", 0, "define fault code", 0);
+
+    // -revert
+    psMetadata *revertArgs = psMetadataAlloc();
+    psMetadataAddS64(revertArgs, PS_LIST_TAIL, "-receive_id", 0, "search on receive_id", 0);
+    psMetadataAddS32(revertArgs, PS_LIST_TAIL, "-fault", 0, "search on fault code", 0);
+    psMetadataAddStr(revertArgs, PS_LIST_TAIL, "-source", 0, "search on source", NULL);
+    psMetadataAddStr(revertArgs, PS_LIST_TAIL, "-product", 0, "search on product", NULL);
+    psMetadataAddStr(revertArgs, PS_LIST_TAIL, "-comment", 0, "search on comment (LIKE)", NULL);
+
+    psMetadata *argSets = psMetadataAlloc();
+    psMetadata *modes = psMetadataAlloc();
+
+    PXOPT_ADD_MODE("-definesource", "", RECEIVETOOL_MODE_DEFINESOURCE, definesourceArgs);
+    PXOPT_ADD_MODE("-list", "", RECEIVETOOL_MODE_LIST, listArgs);
+    PXOPT_ADD_MODE("-definerun", "", RECEIVETOOL_MODE_DEFINERUN, definerunArgs);
+    PXOPT_ADD_MODE("-updatelast", "", RECEIVETOOL_MODE_UPDATELAST, updatelastArgs);
+    PXOPT_ADD_MODE("-pendingrun", "", RECEIVETOOL_MODE_PENDINGRUN, pendingrunArgs);
+    PXOPT_ADD_MODE("-addresult", "", RECEIVETOOL_MODE_ADDRESULT, addresultArgs);
+    PXOPT_ADD_MODE("-revert", "", RECEIVETOOL_MODE_REVERT, revertArgs);
+
+    if (!pxGetOptions(stderr, argc, argv, config, modes, argSets)) {
+        psError(PS_ERR_UNKNOWN, true, "option parsing failed");
+        psFree(argSets);
+        psFree(modes);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+    psFree(modes);
+
+    // define database handle, if used
+    // do this last so we don't setup a connection before CLI options are validated
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
