Index: /trunk/ippTools/src/Makefile.am
===================================================================
--- /trunk/ippTools/src/Makefile.am	(revision 11813)
+++ /trunk/ippTools/src/Makefile.am	(revision 11814)
@@ -8,4 +8,5 @@
 	p4tool \
 	p5tool \
+	p6tool \
 	pxadmin \
 	pxinject \
@@ -27,4 +28,5 @@
 	p4tool.h \
 	p5tool.h \
+	p6tool.h \
 	pxadmin.h \
 	pxinject.h \
@@ -91,4 +93,10 @@
     p5toolConfig.c
 
+p6tool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+p6tool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+p6tool_SOURCES = \
+    p6tool.c \
+    p6toolConfig.c
+
 pxadmin_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
 pxadmin_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
Index: /trunk/ippTools/src/stacktool.c
===================================================================
--- /trunk/ippTools/src/stacktool.c	(revision 11814)
+++ /trunk/ippTools/src/stacktool.c	(revision 11814)
@@ -0,0 +1,1483 @@
+/*
+ * p6tool.c
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * 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 HAVB_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include <ippdb.h>
+
+#include "pxtools.h"
+#include "p6tool.h"
+
+static bool definerunMode(pxConfig *config);
+static bool updaterunMode(pxConfig *config);
+static bool addinputexpMode(pxConfig *config);
+static bool expMode(pxConfig *config);
+static bool imfileMode(pxConfig *config);
+static bool tooverlapMode(pxConfig *config);
+static bool addoverlapMode(pxConfig *config);
+static bool scmapMode(pxConfig *config);
+static bool towarpedMode(pxConfig *config);
+static bool addwarpedMode(pxConfig *config);
+static bool warpedMode(pxConfig *config);
+
+#if 0
+static bool tostackedimfileMode(pxConfig *config);
+//static bool addstackedimfileMode(pxConfig *config);
+static bool stackedimfileMode(pxConfig *config);
+static bool todiffimfileMode(pxConfig *config);
+//static bool adddiffimfileMode(pxConfig *config);
+static bool diffimfileMode(pxConfig *config);
+#endif 
+
+static bool parseAndInsertSkyCellMap(pxConfig *config, const char *mapfile);
+static bool setp4RunState(pxConfig *config, const char *p4_id, const char *state);
+static bool isValidMode(pxConfig *config, const char *mode);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = p6toolConfig(NULL, argc, argv);
+
+    switch (config->mode) {
+        MODECASE(P6TOOL_MODE_DEFINERUN,         definerunMode);
+        MODECASE(P6TOOL_MODE_UPDATERUN,         updaterunMode);
+        MODECASE(P6TOOL_MODE_ADDINPUTEXP,       addinputexpMode);
+        MODECASE(P6TOOL_MODE_EXP,               expMode);
+        MODECASE(P6TOOL_MODE_IMFILE,            imfileMode);
+        MODECASE(P6TOOL_MODE_TOOVERLAP,         tooverlapMode);
+        MODECASE(P6TOOL_MODE_ADDOVERLAP,        addoverlapMode);
+        MODECASE(P6TOOL_MODE_SCMAP,             scmapMode);
+        MODECASE(P6TOOL_MODE_TOWARPED,          towarpedMode);
+        MODECASE(P6TOOL_MODE_ADDWARPED,         addwarpedMode);
+        MODECASE(P6TOOL_MODE_WARPED,            warpedMode);
+
+#if 0
+        MODECASE(P6TOOL_MODE_ADDSCFILE,         addscfileMode);
+        MODECASE(P6TOOL_MODE_SCFILE,            scfileMode);
+        MODECASE(P6TOOL_MODE_TOSTACKEDIMFILE,   tostackedimfileMode);
+//        MODECASE(P6TOOL_MODE_ADDSTACKEDIMFILE,  addstackedimfileMode);
+        MODECASE(P6TOOL_MODE_STACKEDIMFILE,     stackedimfileMode);
+        MODECASE(P6TOOL_MODE_TODIFFIMFILE,      todiffimfileMode);
+//        MODECASE(P6TOOL_MODE_ADDDIFFIMFILE,     adddiffimfileMode);
+        MODECASE(P6TOOL_MODE_DIFFIMFILE,        diffimfileMode);
+#endif
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_FAILURE);
+}
+
+
+static bool definerunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required options
+    bool status = false;
+    psString mode = psMetadataLookupStr(&status, config->args, "-mode");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -survey_mode");
+        return false;
+    }
+    if (!mode) {
+        psError(PS_ERR_UNKNOWN, true, "-mode is required");
+        return false;
+    }
+    // check mode
+    if (mode && !isValidMode(config, mode)) {
+        psError(PS_ERR_UNKNOWN, false, "invalud mode");
+        return false;
+    }
+
+    psString workdir = psMetadataLookupStr(&status, config->args, "-workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -workdir");
+        return false;
+    }
+    if (!workdir) {
+        psError(PS_ERR_UNKNOWN, true, "-workdir is required");
+        return false;
+    }
+
+    psTime *registered = NULL;
+    {
+        psString registeredStr = psMetadataLookupStr(&status, config->args, "-registered");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -registered");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (registeredStr) {
+            registered = psTimeFromISO(registeredStr, PS_TIME_UTC);
+        } else {
+            registered = NULL;
+        }
+    }
+
+    p4RunRow *p4Run = p4RunRowAlloc(
+            0,          // ID
+            mode,
+            "reg",      // state
+            workdir,
+            registered
+    );
+    psFree(registered);
+    if (!p4Run) {
+        psError(PS_ERR_UNKNOWN, false, "failed to alloc p4Run object");
+        return true;
+    }
+    if (!p4RunInsertObject(config->dbh, p4Run)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(p4Run);
+        return true;
+    }
+
+    // get the assigned p4_id
+    p4Run->p4_id = psDBLastInsertID(config->dbh);
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            psFree(p4Run);
+            return false;
+        }
+    }
+
+    if (!p4RunPrintObject(stdout, p4Run, !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print object");
+            psFree(p4Run);
+            return false;
+    }
+
+    psFree(p4Run);
+
+    return true;
+}
+
+
+static bool updaterunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString p4_id = psMetadataLookupStr(&status, config->args, "-p4_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -p4t_id");
+        return false;
+    }
+    if (!p4_id) {
+        psError(PS_ERR_UNKNOWN, true, "-p4_id is required");
+        return false;
+    }
+
+    psString state = psMetadataLookupStr(&status, config->args, "-state");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -state");
+        return false;
+    }
+    if (!state) {
+        psError(PS_ERR_UNKNOWN, true, "-state is required");
+        return false;
+    }
+
+    if (state) {
+        // set detRun.state to state
+        return setp4RunState(config, p4_id, state);
+    }
+
+    return true;
+}
+
+
+static bool addinputexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString p4_id = psMetadataLookupStr(&status, config->args, "-p4_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -p4_id");
+        return false;
+    }
+    if (!p4_id) {
+        psError(PS_ERR_UNKNOWN, true, "-p4_id is required");
+        return false;
+    }
+
+    psString exp_tag = psMetadataLookupStr(&status, config->args, "-exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_tag");
+        return false;
+    }
+    if (!exp_tag) {
+        psError(PS_ERR_UNKNOWN, true, "-exp_tag is required");
+        return false;
+    }
+
+    // defaults to 0
+    psS32 p3_version = psMetadataLookupS32(&status, config->args, "-p3_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -p3_version");
+        return false;
+    }
+
+    // defaults to false
+    bool magiced = psMetadataLookupBool(&status, config->args, "-p3_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -p3_version");
+        return false;
+    }
+
+    // XXX need to validate the p4_id here
+    // XXX instead of validiting it here we should just use forgein key
+    // constrants
+    if (!p4InputExpInsert(config->dbh, 
+            (psS32)atoi(p4_id),
+            exp_tag,
+            p3_version,
+            magiced
+        )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool expMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // find all rawImfiles matching the default query
+    psString query = psStringCopy( 
+        "SELECT\n"
+        "   p3ProcessedExp.*\n"
+        " FROM p4Run\n"
+        " JOIN p4InputExp\n"
+        "   USING(p4_id)\n"
+        " JOIN p3ProcessedExp\n"
+        "   ON p4InputExp.exp_tag = p3ProcessedExp.exp_tag\n"
+        "   AND p4InputExp.p3_version = p3ProcessedExp.p3_version\n"
+        " WHERE\n"
+        "   p4Run.state = 'run'\n" 
+        "   AND p3ProcessedExp.fault = 0\n"
+    );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "p4InputExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psError(PS_ERR_UNKNOWN, false, "no pending rawImfile rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "p4InputExp", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool imfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // find all rawImfiles matching the default query
+    psString query = psStringCopy( 
+        "SELECT\n"
+        "   rawImfile.*\n"
+        " FROM p4Run\n"
+        " JOIN p4InputExp\n"
+        "   USING(p4_id)\n"
+        " JOIN rawImfile\n -- is there any reason not to refer back to rawimfiles?"
+        "   ON p4InputExp.exp_tag = rawImfile.exp_tag\n"
+        " WHERE\n"
+        "   p4Run.state = 'run'\n" 
+    );
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "p4InputExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psError(PS_ERR_UNKNOWN, false, "no pending rawImfile rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "p4InputImfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool tooverlapMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // find all rawImfiles matching the default query
+    psString query = psStringCopy( 
+        "SELECT\n"
+        "   p3ProcessedExp.*\n"
+        " FROM p4Run\n"
+        " JOIN p4InputExp\n"
+        "   USING(p4_id)\n"
+        " JOIN p3ProcessedExp\n"
+        "   ON p4InputExp.exp_tag = p3ProcessedExp.exp_tag\n"
+        "   AND p4InputExp.p3_version = p3ProcessedExp.p3_version\n"
+        " LEFT JOIN p4SkyCellMap\n"
+        "   ON p4InputExp.p4_id = p4SkyCellMap.p4_id\n"
+        "   AND p4InputExp.exp_tag = p4SkyCellMap.exp_tag\n"
+        "   AND p4InputExp.p3_version = p4SkyCellMap.p3_version\n"
+        " WHERE\n"
+        "   p4Run.state = 'run'\n" 
+        "   AND p3ProcessedExp.fault = 0\n"
+        "   AND p4SkyCellMap.p4_id IS NULL\n"
+        "   AND p4SkyCellMap.exp_tag IS NULL\n"
+        "   AND p4SkyCellMap.p3_version IS NULL\n"
+    );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "p4InputExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psError(PS_ERR_UNKNOWN, false, "no pending rawImfile rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "p4InputExp", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool addoverlapMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString mapfile = psMetadataLookupStr(&status, config->args, "-mapfile");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -mapfile");
+        return false;
+    }
+    if (!mapfile) {
+        psError(PS_ERR_UNKNOWN, true, "-mapfile is required");
+        return false;
+    }
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!parseAndInsertSkyCellMap(config, mapfile)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to inject mapfile: %s into the database", mapfile);
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        return false;
+    }
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool parseAndInsertSkyCellMap(pxConfig *config, const char *mapfile)
+{
+    unsigned int nFail = 0;
+    psMetadata *skycells = psMetadataConfigRead(NULL, &nFail, mapfile, false);
+    if (!skycells) {
+        psError(PS_ERR_UNKNOWN, false, "failed to parse mapfile: %s", mapfile);
+        return false;
+    }        
+    if (nFail) {
+        psError(PS_ERR_UNKNOWN, false, "there were %d errors parsing mapfile: %s", nFail, mapfile);
+        psFree(skycells);
+        return false;
+    }
+
+    psMetadataItem *item = NULL;
+    psMetadataIterator *iter = psMetadataIteratorAlloc(skycells, 0, NULL);
+    if ((item = psMetadataGetAndIncrement(iter))) {
+        if (item->type != PS_DATA_METADATA) {
+            psError(PS_ERR_UNKNOWN, false, "mapfile: %s is in the wrong format", mapfile);
+            psFree(iter);
+            psFree(skycells);
+            return false;
+        }
+
+        psMetadata *sc = item->data.md;
+        // this conversion isn't strictly nessicary but it's an easy way of
+        // validating the format
+        p4SkyCellMapRow *row = p4SkyCellMapObjectFromMetadata(sc);
+        if (!row) {
+            psError(PS_ERR_UNKNOWN, false, "failed to convert mapfile: %s metdata entry into a p4SkyCellMap object", mapfile);
+            psFree(iter);
+            psFree(skycells);
+            return false;
+        }
+
+        if (!p4SkyCellMapInsertObject(config->dbh, row)) {
+            psErrorCode err = psErrorCodeLast();
+            switch (err) {
+                case PS_ERR_DB_CLIENT:
+                    psError(PXTOOLS_ERR_SYS, false, "database error");
+                case PS_ERR_DB_SERVER:
+                    psError(PXTOOLS_ERR_PROG, false, "database error");
+                default:
+                    psError(PXTOOLS_ERR_PROG, false, "unknown error");
+            }
+            psFree(row);
+            psFree(iter);
+            psFree(skycells);
+            return false;
+        }
+
+        psFree(row);
+    }
+    psFree(iter);
+    psFree(skycells);
+
+    return true;
+}
+
+
+static bool scmapMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // find all rawImfiles matching the default query
+    psString query = psStringCopy( 
+        "SELECT\n"
+        "   p4SkyCellMap.*\n"
+        " FROM p4Run\n"
+        " JOIN p4SkyCellMap\n"
+        "   USING(p4_id)\n"
+        " WHERE\n"
+        "   p4Run.state = 'run'\n" 
+    );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "p4SkyCellMap");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psError(PS_ERR_UNKNOWN, false, "no p4SkyCellMap rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "p4SkyCellMap", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool towarpedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // find all rawImfiles matching the default query
+    psString query = psStringCopy( 
+        "SELECT\n"
+        "   p4SkyCellMap.*\n"
+        " FROM p4Run\n"
+        " JOIN p4SkyCellMap\n"
+        "   USING(p4_id)\n"
+        " LEFT JOIN p4Scfile\n"
+        "   USING(p4_id, skycell_id, tess_id, exp_tag, p3_version)\n"
+        " WHERE\n"
+        "   p4Run.state = 'run'\n" 
+        "   AND p4Scfile.p4_id IS NULL\n"
+        "   AND p4Scfile.skycell_id IS NULL\n"
+        "   AND p4Scfile.tess_id IS NULL\n"
+        "   AND p4Scfile.exp_tag IS NULL\n"
+        "   AND p4Scfile.p3_version IS NULL\n"
+    );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "p4SkyCellMap");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psError(PS_ERR_UNKNOWN, false, "no p4PendingSkyCell rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "p4PendingSkyCell", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool addwarpedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString p4_id = psMetadataLookupStr(&status, config->args, "-p4_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -p4_id");
+        return false;
+    }
+    if (!p4_id) {
+        psError(PS_ERR_UNKNOWN, true, "-p4_id is required");
+        return false;
+    }
+
+    psString skycell_id = psMetadataLookupStr(&status, config->args, "-skycell_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -skycell_id");
+        return false;
+    }
+    if (!skycell_id) {
+        psError(PS_ERR_UNKNOWN, true, "-skycell_id is required");
+        return false;
+    }
+
+    psString tess_id = psMetadataLookupStr(&status, config->args, "-tess_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -tess_id");
+        return false;
+    }
+    if (!tess_id) {
+        psError(PS_ERR_UNKNOWN, true, "-tess_id is required");
+        return false;
+    }
+
+    psString exp_tag = psMetadataLookupStr(&status, config->args, "-exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_tag");
+        return false;
+    }
+    if (!exp_tag) {
+        psError(PS_ERR_UNKNOWN, true, "-exp_tag is required");
+        return false;
+    }
+
+    psString uri = psMetadataLookupStr(&status, config->args, "-uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -uri");
+        return false;
+    }
+    if (!uri) {
+        psError(PS_ERR_UNKNOWN, true, "-uri is required");
+        return false;
+    }
+
+    // defaults to 0
+    psS32 p3_version = psMetadataLookupS32(&status, config->args, "-p3_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -p3_version");
+        return false;
+    }
+
+    // optional
+    psF64 bg = psMetadataLookupF64(&status, config->args, "-bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg");
+        return false;
+    }
+
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, config->args, "-bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_mean_stdev");
+        return false;
+    }
+
+
+    // XXX need to validate that this coresponds to an p4InputImfile
+    if (!p4ScfileInsert(config->dbh, 
+            (psS32)atoi(p4_id),
+            skycell_id,
+            tess_id,
+            exp_tag,
+            p3_version,
+            uri,
+            bg,
+            bg_mean_stdev
+        )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool warpedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // find all rawImfiles matching the default query
+    psString query = psStringCopy( 
+        "SELECT\n"
+        "   p4Scfile.*\n"
+        " FROM p4Run\n"
+        " JOIN p4Scfile\n"
+        "   USING(p4_id)\n"
+        " WHERE\n"
+        "   p4Run.state = 'run'\n" 
+    );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "p4Scfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psError(PS_ERR_UNKNOWN, false, "no p4Scfile rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "p4Scfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+#if 0
+static bool tostackedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    psString query = psStringCopy(
+        " SELECT DISTINCT"
+        "   p4WarpedImfile.*,"
+        "   p4Run.workdir"
+        " FROM p4Run"
+        " JOIN p4WarpedImfile"
+        "   USING(p4_id)"
+        " LEFT JOIN p4StackedImfile"
+        "   USING(p4_id, class_id)"
+        " WHERE"
+        "   p4Run.state = 'run'"
+        "   AND p4StackedImfile.p4_id IS NULL"
+        "   AND p4Stackedmfile.class_id IS NULL"
+    );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "p4WarpedImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(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)) {
+        psError(PS_ERR_UNKNOWN, false, "no p4WarpedImfile rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "p4WarpedImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool addstackedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    bool status = false;
+    psString p4_id = psMetadataLookupStr(&status, config->args, "-p4_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -p4_id");
+        return false;
+    }
+    if (!p4_id) {
+        psError(PS_ERR_UNKNOWN, true, "-p4_id is required");
+        return false;
+    }
+
+    psString class_id = psMetadataLookupStr(&status, config->args, "-class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -class_id");
+        return false;
+    }
+    if (!class_id) {
+        psError(PS_ERR_UNKNOWN, true, "-class_id is required");
+        return false;
+    }
+
+    psString uri = psMetadataLookupStr(&status, config->args, "-uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -uri");
+        return false;
+    }
+    if (!uri) {
+        psError(PS_ERR_UNKNOWN, true, "-uri is required");
+        return false;
+    }
+
+    // optional
+    psString b1_uri = psMetadataLookupStr(&status, config->args, "-b1_uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -b1_uri");
+        return false;
+    }
+
+    psString b2_uri = psMetadataLookupStr(&status, config->args, "-b2_uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -b2_uri");
+        return false;
+    }
+
+    // XXX need to validate that this coresponds to an p4WarpedImfile
+    if (!p4StackedImfileInsert(config->dbh, 
+            (psS32)atoi(p4_id),
+            class_id,
+            uri,
+            b1_uri,
+            b2_uri
+        )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool stackedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    psString query = psStringCopy(
+        " SELECT DISTINCT"
+        "   p4StackedImfile.*"
+        " FROM p4Run"
+        " JOIN p4StackedImfile"
+        "   USING(p4_id)"
+        " WHERE"
+        "   p4Run.state = 'run'"
+    );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "p4StackedImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(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)) {
+        psError(PS_ERR_UNKNOWN, false, "no p4StackedImfile rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "p4StackedImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool todiffimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    psString query = psStringCopy(
+        " SELECT DISTINCT"
+        "   p4StackedImfile.*,"
+        "   p4WarpedImfile.uri as warped_uri,"
+        "   p4Run.workdir"
+        " FROM p4Run"
+        " JOIN p4StackedImfile"
+        "   USING(p4_id)"
+        " JOIN p4WarpedImfile"
+        "   USING(p4_id, class_id)"
+        " LEFT JOIN p4DiffImfile"
+        "   ON p4WarpedImfile.p4_id = p4DiffImfile.p4_id"
+        "   AND p4WarpedImfile.exp_tag = p4DiffImfile.exp_tag"
+        "   AND p4WarpedImfile.p3_version = p4DiffImfile.p3_version"
+        "   AND p4WarpedImfile.class_id = p4DiffImfile.class_id"
+        " WHERE"
+        "   p4Run.state = 'run'"
+        "   AND p4DiffImfile.p4_id IS NULL"
+        "   AND p4DiffImfile.exp_tag IS NULL"
+        "   AND p4DiffImfile.p3_version IS NULL"
+        "   AND p4DiffImfile.class_id IS NULL"
+    );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "p4StackedImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(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)) {
+        psError(PS_ERR_UNKNOWN, false, "no p4StackedImfile rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "p4StackedImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool adddiffimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    bool status = false;
+    psString p4_id = psMetadataLookupStr(&status, config->args, "-p4_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -p4_id");
+        return false;
+    }
+    if (!p4_id) {
+        psError(PS_ERR_UNKNOWN, true, "-p4_id is required");
+        return false;
+    }
+
+    psString exp_tag = psMetadataLookupStr(&status, config->args, "-exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_tag");
+        return false;
+    }
+    if (!exp_tag) {
+        psError(PS_ERR_UNKNOWN, true, "-exp_tag is required");
+        return false;
+    }
+
+    psString class_id = psMetadataLookupStr(&status, config->args, "-class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -class_id");
+        return false;
+    }
+    if (!class_id) {
+        psError(PS_ERR_UNKNOWN, true, "-class_id is required");
+        return false;
+    }
+
+    psString uri = psMetadataLookupStr(&status, config->args, "-uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -uri");
+        return false;
+    }
+    if (!uri) {
+        psError(PS_ERR_UNKNOWN, true, "-uri is required");
+        return false;
+    }
+
+    // defaults to 0
+    psS32 p3_version = psMetadataLookupS32(&status, config->args, "-p3_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -p3_version");
+        return false;
+    }
+
+    // optional
+    psString b1_uri = psMetadataLookupStr(&status, config->args, "-b1_uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -b1_uri");
+        return false;
+    }
+
+    psString b2_uri = psMetadataLookupStr(&status, config->args, "-b2_uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -b2_uri");
+        return false;
+    }
+
+    // XXX need to validate that this coresponds to an p4InputImfile
+    if (!p4DiffImfileInsert(config->dbh, 
+            (psS32)atoi(p4_id),
+            exp_tag,
+            p3_version,
+            class_id,
+            uri,
+            b1_uri,
+            b2_uri
+        )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool diffimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    psString query = psStringCopy(
+        " SELECT DISTINCT"
+        "   p4DiffImfile.*"
+        " FROM p4Run"
+        " JOIN p4DiffImfile"
+        "   USING(p4_id)"
+        " WHERE"
+        "   p4Run.state = 'run'"
+    );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "p4DiffImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(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)) {
+        psError(PS_ERR_UNKNOWN, false, "no p4DiffImfile rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "p4DiffImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+#endif
+
+static bool setp4RunState(pxConfig *config, const char *p4_id, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(p4_id, false);
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(state, "run", 4) == 0)
+            || (strncmp(state, "stop", 5) == 0)
+            || (strncmp(state, "reg", 4) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid p4Run state: %s", state);
+        return false;
+    }
+
+    char *query = "UPDATE p4Run SET state = '%s' WHERE p4_id = '%s'";
+    if (!p_psDBRunQuery(config->dbh, query, state, p4_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to change state for p4_id %s", p4_id);
+        return false;
+    }
+
+    return true;
+}
+
+static bool isValidMode(pxConfig *config, const char *mode)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(mode, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(mode, "warp", 5) == 0)
+            || (strncmp(mode, "diff", 5) == 0)
+            || (strncmp(mode, "stack", 6) == 0)
+            || (strncmp(mode, "magic", 6) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid detRun mode: %s", mode);
+        return false;
+    }
+
+    return true;
+}
Index: /trunk/ippTools/src/stacktool.h
===================================================================
--- /trunk/ippTools/src/stacktool.h	(revision 11814)
+++ /trunk/ippTools/src/stacktool.h	(revision 11814)
@@ -0,0 +1,42 @@
+/*
+ * p6tool.h
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * 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 P6TOOL_H
+#define P6TOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    P6TOOL_MODE_NONE           = 0x0,
+    P6TOOL_MODE_DEFINERUN,
+    P6TOOL_MODE_UPDATERUN,
+    P6TOOL_MODE_ADDINPUTEXP,
+    P6TOOL_MODE_EXP,
+    P6TOOL_MODE_IMFILE,
+    P6TOOL_MODE_TOOVERLAP,
+    P6TOOL_MODE_ADDOVERLAP,
+    P6TOOL_MODE_SCMAP,
+    P6TOOL_MODE_TOWARPED,
+    P6TOOL_MODE_ADDWARPED,
+    P6TOOL_MODE_WARPED,
+} p6toolMode;
+
+pxConfig *p6toolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // P6TOOL_H
Index: /trunk/ippTools/src/stacktoolConfig.c
===================================================================
--- /trunk/ippTools/src/stacktoolConfig.c	(revision 11814)
+++ /trunk/ippTools/src/stacktoolConfig.c	(revision 11814)
@@ -0,0 +1,387 @@
+/*
+ * p6toolConfig.c
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * 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 "p6tool.h"
+
+// this function can not fail -- exits on error
+pxConfig *p6toolConfig(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");
+        goto FAIL;
+    }
+
+    psTime *time = psTimeGetNow(PS_TIME_TAI);
+    psString now = psTimeToISO(time);
+    psFree(time);
+
+    // -definerun
+    psMetadata *definerunArgs = psMetadataAlloc();
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-mode", 0,
+            "define mode (required)", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-workdir", 0,
+            "define workdir (required)", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-registered",  0,
+            "time detrend run was registered", now);
+    psMetadataAddBool(definerunArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+ 
+    // -updaterun
+    psMetadata *updaterunArgs = psMetadataAlloc();
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-p4_id", 0,
+            "define p4 ID (required)", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-state", 0,
+            "set state (required)", NULL);
+#if 0
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-workdir", 0,
+            "define workdir (required)", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-registered",  0,
+            "time detrend run was registered", now);
+#endif
+
+    // -addinputexp
+    psMetadata *addinputexpArgs = psMetadataAlloc();
+    psMetadataAddStr(addinputexpArgs, PS_LIST_TAIL, "-p4_id", 0,
+            "define p4 ID (required)", NULL);
+    psMetadataAddStr(addinputexpArgs, PS_LIST_TAIL, "-exp_tag", 0,
+            "define exposure tag (required)", NULL);
+    psMetadataAddS32(addinputexpArgs, PS_LIST_TAIL, "-p3_version", 0,
+            "define phase 3 version of exposure tag (required)", 0);
+    psMetadataAddBool(addinputexpArgs, PS_LIST_TAIL, "-magiced",  0,
+            "has this exposure been magiced", false);
+
+    // -exp
+    psMetadata *expArgs = psMetadataAlloc();
+    psMetadataAddStr(expArgs, PS_LIST_TAIL, "-p4_id", 0,
+            "define p4 ID (required)", NULL);
+    psMetadataAddStr(expArgs, PS_LIST_TAIL, "-exp_tag", 0,
+            "define exposure tag (required)", NULL);
+    psMetadataAddS32(expArgs, PS_LIST_TAIL, "-p3_version", 0,
+            "define phase 3 version of exposure tag (required)", 0);
+    psMetadataAddU64(expArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(expArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -imfile
+    psMetadata *imfileArgs = psMetadataAlloc();
+    psMetadataAddStr(imfileArgs, PS_LIST_TAIL, "-p4_id", 0,
+            "define p4 ID (required)", NULL);
+    psMetadataAddStr(imfileArgs, PS_LIST_TAIL, "-exp_tag", 0,
+            "define exposure tag (required)", NULL);
+    psMetadataAddS32(imfileArgs, PS_LIST_TAIL, "-p3_version", 0,
+            "define phase 3 version of exposure tag (required)", 0);
+    psMetadataAddU64(imfileArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(imfileArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -tooverlap
+    psMetadata *tooverlapArgs = psMetadataAlloc();
+    psMetadataAddStr(tooverlapArgs, PS_LIST_TAIL, "-p4_id", 0,
+            "search by p4 ID", NULL);
+    psMetadataAddU64(tooverlapArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(tooverlapArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -addoverlap
+    psMetadata *addoverlapArgs = psMetadataAlloc();
+    psMetadataAddStr(addoverlapArgs, PS_LIST_TAIL, "-mapfile", 0,
+            "skycell <-> imfile mapping description", NULL);
+
+    // -scmap
+    psMetadata *scmapArgs = psMetadataAlloc();
+    psMetadataAddStr(scmapArgs, PS_LIST_TAIL, "-p4_id", 0,
+            "search by p4 ID", NULL);
+    psMetadataAddStr(scmapArgs, PS_LIST_TAIL, "-skycell_id", 0,
+            "searcy by skycell ID", NULL);
+    psMetadataAddStr(scmapArgs, PS_LIST_TAIL, "-tess_id", 0,
+            "searcy by tess ID", NULL);
+    psMetadataAddU64(scmapArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(scmapArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -towarped
+    psMetadata *towarpedArgs = psMetadataAlloc();
+    psMetadataAddStr(towarpedArgs, PS_LIST_TAIL, "-p4_id", 0,
+            "search by p4 ID", NULL);
+    psMetadataAddU64(towarpedArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(towarpedArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+ 
+    // -addwarped
+    psMetadata *addwarpedArgs = psMetadataAlloc();
+    psMetadataAddStr(addwarpedArgs, PS_LIST_TAIL, "-p4_id", 0,
+            "define p4 ID (required)", NULL);
+    psMetadataAddStr(addwarpedArgs, PS_LIST_TAIL, "-skycell_id",  0,
+            "define skycell ID (required)", NULL);
+    psMetadataAddStr(addwarpedArgs, PS_LIST_TAIL, "-tess_id",  0,
+            "define tessellation ID (required)", NULL);
+    psMetadataAddStr(addwarpedArgs, PS_LIST_TAIL, "-exp_tag", 0,
+            "define exposure tag (required)", NULL);
+    psMetadataAddS32(addwarpedArgs, PS_LIST_TAIL, "-p3_version", 0,
+            "define phase 3 version of exposure tag (required)", 0);
+    psMetadataAddStr(addwarpedArgs, PS_LIST_TAIL, "-uri", 0,
+            "define URI of file (required)", 0);
+    psMetadataAddF64(addwarpedArgs, PS_LIST_TAIL, "-bg",  0,
+            "define exposue background", NAN);
+    psMetadataAddF64(addwarpedArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,
+            "define exposue background mean stdev", NAN);
+ 
+    // -warped
+    psMetadata *warpedArgs = psMetadataAlloc();
+    psMetadataAddStr(warpedArgs, PS_LIST_TAIL, "-p4_id", 0,
+            "search by p4 ID", NULL);
+    psMetadataAddStr(warpedArgs, PS_LIST_TAIL, "-skycell_id",  0,
+            "define skycell ID (required)", NULL);
+    psMetadataAddStr(warpedArgs, PS_LIST_TAIL, "-tess_id",  0,
+            "define tessellation ID (required)", NULL);
+    psMetadataAddStr(warpedArgs, PS_LIST_TAIL, "-exp_tag", 0,
+            "define exposure tag (required)", NULL);
+    psMetadataAddS32(warpedArgs, PS_LIST_TAIL, "-p3_version", 0,
+            "define phase 3 version of exposure tag (required)", 0);
+    psMetadataAddU64(warpedArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(warpedArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+#if 0
+    // -tostackedimfile
+    psMetadata *tostackedimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(tostackedimfileArgs, PS_LIST_TAIL, "-p4_id", 0,
+            "search by p4 ID", NULL);
+    psMetadataAddBool(tostackedimfileArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -addstackedimfile
+    psMetadata *addstackedimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(addstackedimfileArgs, PS_LIST_TAIL, "-p4_id", 0,
+            "define p4 ID (required)", NULL);
+    psMetadataAddStr(addstackedimfileArgs, PS_LIST_TAIL, "-exp_tag", 0,
+            "define exposure tag (required)", NULL);
+    psMetadataAddS32(addstackedimfileArgs, PS_LIST_TAIL, "-p3_version", 0,
+            "define phase 3 version of exposure tag (required)", 0);
+    psMetadataAddStr(addstackedimfileArgs, PS_LIST_TAIL, "-class_id", 0,
+            "define class ID (required)", 0);
+    psMetadataAddStr(addstackedimfileArgs, PS_LIST_TAIL, "-uri", 0,
+            "define URI of file (required)", 0);
+    psMetadataAddStr(addstackedimfileArgs, PS_LIST_TAIL, "-b1_uri",  0,
+            "define banana 1", NULL);
+    psMetadataAddStr(addstackedimfileArgs, PS_LIST_TAIL, "-b2_uri",  0,
+            "define banana 2", NULL);
+
+    // -stackedimfile
+    psMetadata *stackedimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(stackedimfileArgs, PS_LIST_TAIL, "-p4_id", 0,
+            "search by p4 ID", NULL);
+    psMetadataAddBool(stackedimfileArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+ 
+    // -todiffimfile
+    psMetadata *todiffimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(todiffimfileArgs, PS_LIST_TAIL, "-p4_id", 0,
+            "search by p4 ID", NULL);
+    psMetadataAddBool(todiffimfileArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+ 
+    // -adddiffimfile
+    psMetadata *adddiffimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(adddiffimfileArgs, PS_LIST_TAIL, "-p4_id", 0,
+            "define p4 ID (required)", NULL);
+    psMetadataAddStr(adddiffimfileArgs, PS_LIST_TAIL, "-exp_tag", 0,
+            "define exposure tag (required)", NULL);
+    psMetadataAddS32(adddiffimfileArgs, PS_LIST_TAIL, "-p3_version", 0,
+            "define phase 3 version of exposure tag (required)", 0);
+    psMetadataAddStr(adddiffimfileArgs, PS_LIST_TAIL, "-class_id", 0,
+            "define class ID (required)", 0);
+    psMetadataAddStr(adddiffimfileArgs, PS_LIST_TAIL, "-uri", 0,
+            "define URI of file (required)", 0);
+    psMetadataAddStr(adddiffimfileArgs, PS_LIST_TAIL, "-b1_uri",  0,
+            "define banana 1", NULL);
+    psMetadataAddStr(adddiffimfileArgs, PS_LIST_TAIL, "-b2_uri",  0,
+            "define banana 2", NULL);
+
+    // -diffimfile
+    psMetadata *diffimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(diffimfileArgs, PS_LIST_TAIL, "-p4_id", 0,
+            "search by p4 ID", NULL);
+    psMetadataAddBool(diffimfileArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+#endif 
+
+    psFree(now);
+
+#define PXTOOL_MODE(option, modeval, argset) \
+{ \
+    int N = 0; \
+    if ((N = psArgumentGet (argc, argv, option))) { \
+        psArgumentRemove (N, &argc, argv); \
+        if (config->mode) { \
+            psError(PS_ERR_UNKNOWN, true, "only one mode selection is allowed"); \
+            goto FAIL; \
+        } \
+        config->mode = modeval; \
+        config->args = psMemIncrRefCounter(argset); \
+    } \
+    if (!psMetadataAddMetadata(argSets, PS_LIST_TAIL, option, 0, NULL, argset)) {;\
+        psError(PS_ERR_UNKNOWN, false, "failed to add argset for %s", option); \
+    } \
+    psFree(argset); \
+}
+
+    psMetadata *argSets = psMetadataAlloc();
+    // find which mode we're running under
+    PXTOOL_MODE("-definerun",       P6TOOL_MODE_DEFINERUN,      definerunArgs);
+    PXTOOL_MODE("-updaterun",       P6TOOL_MODE_UPDATERUN,      updaterunArgs);
+    PXTOOL_MODE("-addinputexp",     P6TOOL_MODE_ADDINPUTEXP,    addinputexpArgs);
+    PXTOOL_MODE("-exp",             P6TOOL_MODE_EXP,            expArgs);
+    PXTOOL_MODE("-imfile",          P6TOOL_MODE_IMFILE,         imfileArgs);
+    PXTOOL_MODE("-tooverlap",       P6TOOL_MODE_TOOVERLAP,      tooverlapArgs);
+    PXTOOL_MODE("-addoverlap",      P6TOOL_MODE_ADDOVERLAP,     addoverlapArgs);
+    PXTOOL_MODE("-scmap",           P6TOOL_MODE_SCMAP,          scmapArgs);
+    PXTOOL_MODE("-towarped",        P6TOOL_MODE_TOWARPED,       towarpedArgs);
+    PXTOOL_MODE("-addwarped",       P6TOOL_MODE_ADDWARPED,      addwarpedArgs);
+    PXTOOL_MODE("-warped",          P6TOOL_MODE_WARPED,         warpedArgs);
+#if 0
+    PXTOOL_MODE("-tostackedimfile", P6TOOL_MODE_TOSTACKEDIMFILE, tostackedimfileArgs);
+    PXTOOL_MODE("-addstackedimfile", P6TOOL_MODE_ADDSTACKEDIMFILE, addstackedimfileArgs);
+    PXTOOL_MODE("-stackedimfile",   P6TOOL_MODE_STACKEDIMFILE, stackedimfileArgs);
+    PXTOOL_MODE("-todiffimfile", P6TOOL_MODE_TODIFFIMFILE, todiffimfileArgs);
+    PXTOOL_MODE("-adddiffimfile", P6TOOL_MODE_ADDDIFFIMFILE, adddiffimfileArgs);
+    PXTOOL_MODE("-diffimfile",   P6TOOL_MODE_DIFFIMFILE, diffimfileArgs);
+#endif
+
+    bool argErr = false;
+    if (config->mode == P6TOOL_MODE_NONE) {
+        argErr = true;
+        fprintf (stderr, "mode argument is required\n");
+    } else if (! psArgumentParse(config->args, &argc, argv) || argc != 1) {
+        argErr = true;
+        fprintf (stderr, "error parsing arguments\n");
+    }
+
+    if (argErr) {
+        printf("\nPan-STARRS Phase 4 Tool\n");
+        printf("Usage: %s <mode> [<options>]\n\n", argv[0]);
+        printf(" <mode> :\n\n"); 
+
+        psMetadataIterator *iter = psMetadataIteratorAlloc(argSets, 0, NULL);
+        psMetadataItem *item = NULL;
+        while ((item = psMetadataGetAndIncrement(iter))) {
+            if (!item->type == PS_DATA_METADATA) {
+                psAbort("all options must be specified as a metadata");
+            }
+
+            fprintf(stdout, "%s ", item->name);
+            psArgumentHelp(item->data.md);
+        }
+        psFree(iter);
+
+        psFree(argSets);
+        goto FAIL;
+    }
+
+    psFree(argSets);
+
+    // setup search criterion
+#define addWhereStr(name) \
+{ \
+    psString str = NULL; \
+    bool status = false; \
+    if ((str = psMetadataLookupStr(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddStr(config->where, PS_LIST_TAIL, #name, 0, "==", str)) {\
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            goto FAIL; \
+        } \
+    } \
+}
+
+#define addWhereS32(name) \
+{ \
+    psS32 s32 = 0; \
+    bool status = false; \
+    if ((s32= psMetadataLookupS32(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddS32(config->where, PS_LIST_TAIL, #name, 0, "==", s32)) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            goto FAIL; \
+        } \
+    } \
+}
+
+
+    // generate SQL where clause
+    config->where = psMetadataAlloc();
+
+{
+    psString str = NULL;
+    bool status = false;
+    if ((str = psMetadataLookupStr(&status, config->args, "-p4_id"))) {
+        if (!psMetadataAddS32(config->where, PS_LIST_TAIL, "p4_id", 0, "==", (psS32)atoi(str))) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item p4_id");
+            goto FAIL; 
+        } 
+    } 
+}
+    addWhereStr(skycell_id);
+    addWhereStr(tess_id);
+    addWhereStr(exp_tag);
+
+    if (config->where->list->n < 1) {
+        psFree(config->where);
+        config->where = NULL;
+    }
+
+    // define Database handle, if used
+    // do this last so we don't setup a connection before CLI options are
+    // validated
+    config->dbh = pmConfigDB(config->modules);
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        goto FAIL;
+    }
+
+    // save argv/argc
+    config->argv = argv;
+    config->argc = argc;
+
+    return config;
+
+FAIL:
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+    exit(EXIT_FAILURE);
+}
