Index: /trunk/dbconfig/changes.txt
===================================================================
--- /trunk/dbconfig/changes.txt	(revision 28342)
+++ /trunk/dbconfig/changes.txt	(revision 28343)
@@ -1757,2 +1757,36 @@
 
 ALTER TABLE pstampRequest ADD COLUMN timestamp TIMESTAMP AFTER outdir;
+
+
+-- Tables to support (re-)photometry of a diff
+CREATE TABLE diffPhotRun (
+    diff_phot_id BIGINT AUTO_INCREMENT, -- Identifier for diffPhotRun
+    diff_id BIGINT NOT NULL,            -- Identifier for diffRun
+    state VARCHAR(64) NOT NULL,         -- State of run
+    workdir VARCHAR(255) NOT NULL, -- working directory
+    label VARCHAR(64),             -- processing label
+    data_group VARCHAR(64),        -- group for data
+    reduction VARCHAR(64),         -- reduction class (for altering recipe)
+    registered TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- time run was registered
+    note VARCHAR(255),             -- note
+    PRIMARY KEY(diff_phot_id),
+    KEY(diff_id),
+    KEY(state),
+    KEY(label),
+    KEY(data_group),
+    FOREIGN KEY(diff_id) REFERENCES diffRun(diff_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+CREATE TABLE diffPhotSkyfile (
+    diff_phot_id BIGINT AUTO_INCREMENT, -- Identifier for diffPhotRun
+    skycell_id VARCHAR(64) NOT NULL,            -- Skycell identifier
+    path_base VARCHAR(255) NOT NULL, -- Base of path for output
+    dtime_script FLOAT,              -- elapsed time for script
+    hostname VARCHAR(64) NOT NULL,   -- host that executed script
+    fault SMALLINT NOT NULL,         -- fault code
+    quality SMALLINT NOT NULL,       -- bad quality flag
+    software_ver VARCHAR(16),                       -- software version for run
+    PRIMARY KEY(diff_phot_id, skycell_id),
+    KEY(fault),
+    KEY(quality),
+    FOREIGN KEY(diff_phot_id) REFERENCES diffPhotRun(diff_phot_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
Index: /trunk/dbconfig/diffphot.md
===================================================================
--- /trunk/dbconfig/diffphot.md	(revision 28343)
+++ /trunk/dbconfig/diffphot.md	(revision 28343)
@@ -0,0 +1,23 @@
+			
+diffPhotRun METADATA
+    diff_phot_id    S64         0       # Primary Key AUTO_INCREMENT
+    diff_id	    S64		0	# Key
+    state           STR         64      # Key
+    workdir         STR         255
+    label           STR         64      # Key
+    data_group      STR         64      # Key
+    reduction       STR         64
+    registered      TAI         NULL
+    note            STR         255
+END
+
+diffPhotSkyfile METADATA
+    diff_phot_id     S64        0       # Primary Key
+    skycell_id       STR        64      # Primary Key
+    path_base        STR        255
+    dtime_script     F32        0.0
+    hostname         STR        64
+    fault            S16        0       # Key
+    quality          S16        0       # Key
+    software_ver     STR        16
+END
Index: /trunk/dbconfig/ipp.m4
===================================================================
--- /trunk/dbconfig/ipp.m4	(revision 28342)
+++ /trunk/dbconfig/ipp.m4	(revision 28343)
@@ -33,2 +33,3 @@
 include(receive.md)
 include(publish.md)
+include(diffphot.md)
Index: /trunk/ippTools/share/Makefile.am
===================================================================
--- /trunk/ippTools/share/Makefile.am	(revision 28342)
+++ /trunk/ippTools/share/Makefile.am	(revision 28343)
@@ -325,3 +325,8 @@
      warptool_towarped.sql \
      warptool_updateskyfile.sql \
-     warptool_warped.sql
+     warptool_warped.sql \
+	diffphottool_definerun.sql \
+	diffphottool_input.sql \
+	diffphottool_pending.sql \
+	diffphottool_advance.sql \
+	diffphottool_revert.sql
Index: /trunk/ippTools/share/diffphottool_advance.sql
===================================================================
--- /trunk/ippTools/share/diffphottool_advance.sql	(revision 28343)
+++ /trunk/ippTools/share/diffphottool_advance.sql	(revision 28343)
@@ -0,0 +1,15 @@
+UPDATE diffPhotRun
+SET state = 'full'
+WHERE diff_phot_id IN (
+    SELECT
+        diff_phot_id
+    FROM diffPhotRun
+    JOIN diffSkyfile USING(diff_id)
+    JOIN diffPhotSkyfile USING(diff_phot_id)
+    WHERE diffPhotRun.state = 'new'
+        AND diffPhotSkyfile.fault = 0
+        AND diffSkyfile.fault = 0
+        AND diffSkyfile.quality = 0
+        -- WHERE hook %s
+    HAVING COUNT(diffPhotSkyfile.skycell_id) = COUNT(diffSkyfile.skycell_id)
+) AS diffPhotRunsDone
Index: /trunk/ippTools/share/diffphottool_definerun.sql
===================================================================
--- /trunk/ippTools/share/diffphottool_definerun.sql	(revision 28343)
+++ /trunk/ippTools/share/diffphottool_definerun.sql	(revision 28343)
@@ -0,0 +1,10 @@
+SELECT DISTINCT
+    diffRun.*
+FROM diffRun
+JOIN diffInputSkyfile USING(diff_id)
+JOIN warpRun ON warpRun.warp_id = diffInputSkyfile.warp1 -- only JOINing inputs, not templates!
+JOIN fakeRun USING(fake_id)
+JOIN camRun USING(cam_id)
+JOIN chipRun USING(chip_id)
+JOIN rawExp USING(exp_id)
+WHERE diffRun.state = 'full'
Index: /trunk/ippTools/share/diffphottool_input.sql
===================================================================
--- /trunk/ippTools/share/diffphottool_input.sql	(revision 28343)
+++ /trunk/ippTools/share/diffphottool_input.sql	(revision 28343)
@@ -0,0 +1,6 @@
+SELECT
+    diffPhotRun.*,
+    diffSkyfile.path_base
+FROM diffPhotRun
+JOIN diffRun USING(diff_id)
+JOIN diffSkyfile USING(diff_id)
Index: /trunk/ippTools/share/diffphottool_pending.sql
===================================================================
--- /trunk/ippTools/share/diffphottool_pending.sql	(revision 28343)
+++ /trunk/ippTools/share/diffphottool_pending.sql	(revision 28343)
@@ -0,0 +1,11 @@
+SELECT
+    diffPhotRun.*,
+    diffSkyfile.skycell_id
+FROM diffPhotRun
+JOIN diffRun USING(diff_id)
+JOIN diffSkyfile USING(diff_id)
+LEFT JOIN diffPhotSkyfile USING(diff_phot_id, skycell_id)
+WHERE diffPhotSkyfile.skycell_id IS NULL
+    AND diffRun.state = 'full'
+    AND diffSkyfile.fault = 0
+    AND diffSkyfile.quality = 0
Index: /trunk/ippTools/share/diffphottool_revert.sql
===================================================================
--- /trunk/ippTools/share/diffphottool_revert.sql	(revision 28343)
+++ /trunk/ippTools/share/diffphottool_revert.sql	(revision 28343)
@@ -0,0 +1,4 @@
+DELETE diffPhotSkyfile
+FROM diffRun
+JOIN diffPhotSkyfile USING(diff_phot_id)
+WHERE fault != 0
Index: /trunk/ippTools/share/pxadmin_create_tables.sql
===================================================================
--- /trunk/ippTools/share/pxadmin_create_tables.sql	(revision 28342)
+++ /trunk/ippTools/share/pxadmin_create_tables.sql	(revision 28343)
@@ -994,5 +994,5 @@
         tess_id VARCHAR(64),
         filter VARCHAR(64),
-	software_ver VARCHAR(16),
+        software_ver VARCHAR(16),
         note VARCHAR(255),
         PRIMARY KEY(stack_id),
@@ -1042,5 +1042,5 @@
         quality SMALLINT NOT NULL DEFAULT 0,
         fault SMALLINT,
-	software_ver VARCHAR(16),
+        software_ver VARCHAR(16),
         PRIMARY KEY(stack_id),
         KEY(dtime_stack),
@@ -1065,5 +1065,5 @@
         exposure TINYINT DEFAULT 0,
         magicked BIGINT,
-	software_ver VARCHAR(16),
+        software_ver VARCHAR(16),
         maskfrac_npix FLOAT,
         maskfrac_static FLOAT,
@@ -1134,9 +1134,9 @@
         fault SMALLINT,
         magicked BIGINT,
-	software_ver VARCHAR(16),
-	maskfrac_npix INT,
-	maskfrac_static FLOAT,
-	maskfrac_dynamic FLOAT,
-	maskfrac_magic FLOAT,
+        software_ver VARCHAR(16),
+        maskfrac_npix INT,
+        maskfrac_static FLOAT,
+        maskfrac_dynamic FLOAT,
+        maskfrac_magic FLOAT,
         maskfrac_advisory FLOAT,
         PRIMARY KEY(diff_id, skycell_id),
@@ -1678,4 +1678,41 @@
 ) ENGINE=innodb DEFAULT CHARSET=latin1;
 
+
+-- Tables to support (re-)photometry of a diff
+
+CREATE TABLE diffPhotRun (
+    diff_phot_id BIGINT AUTO_INCREMENT, -- Identifier for diffPhotRun
+    diff_id BIGINT NOT NULL,            -- Identifier for diffRun
+    state VARCHAR(64) NOT NULL,         -- State of run
+    workdir VARCHAR(255) NOT NULL, -- working directory
+    label VARCHAR(64),             -- processing label
+    data_group VARCHAR(64),        -- group for data
+    reduction VARCHAR(64),         -- reduction class (for altering recipe)
+    registered TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- time run was registered
+    note VARCHAR(255),             -- note
+    PRIMARY KEY(diff_phot_id),
+    KEY(diff_id),
+    KEY(state),
+    KEY(label),
+    KEY(data_group),
+    FOREIGN KEY(diff_id) REFERENCES diffRun(diff_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE diffPhotSkyfile (
+    diff_phot_id BIGINT AUTO_INCREMENT, -- Identifier for diffPhotRun
+    skycell_id VARCHAR(64) NOT NULL,            -- Skycell identifier
+    path_base VARCHAR(255) NOT NULL, -- Base of path for output
+    dtime_script FLOAT,              -- elapsed time for script
+    hostname VARCHAR(64) NOT NULL,   -- host that executed script
+    fault SMALLINT NOT NULL,         -- fault code
+    quality SMALLINT NOT NULL,       -- bad quality flag
+    software_ver VARCHAR(16),                       -- software version
+    PRIMARY KEY(diff_phot_id, skycell_id),
+    KEY(state),
+    KEY(fault),
+    KEY(quality),
+    FOREIGN KEY(diff_phot_id) REFERENCES diffPhotRun(diff_phot_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
 -- This comment line is here to avoid empty query error.
 -- Another way to avoid that problem is to omit the semicolon above but I think that is untidy.
Index: /trunk/ippTools/src/Makefile.am
===================================================================
--- /trunk/ippTools/src/Makefile.am	(revision 28342)
+++ /trunk/ippTools/src/Makefile.am	(revision 28343)
@@ -25,5 +25,6 @@
 	warptool \
 	receivetool \
-	pubtool
+	pubtool \
+	diffphottool
 
 pkginclude_HEADERS = \
@@ -69,5 +70,6 @@
 	staticskytool.h \
 	warptool.h \
-	pubtool.h
+	pubtool.h \
+	diffphottool.h
 
 lib_LTLIBRARIES = libpxtools.la
@@ -264,4 +266,10 @@
     pubtoolConfig.c
 
+diffphottool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+diffphottool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+diffphottool_SOURCES = \
+    diffphottool.c \
+    diffphottoolConfig.c
+
 clean-local:
 	-rm -f TAGS
Index: /trunk/ippTools/src/diffphottool.c
===================================================================
--- /trunk/ippTools/src/diffphottool.c	(revision 28343)
+++ /trunk/ippTools/src/diffphottool.c	(revision 28343)
@@ -0,0 +1,459 @@
+/*
+ * diffphottool.c
+ *
+ * Copyright (C) 2007-2010  Joshua Hoblitt, Paul Price
+ *
+ * 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 "diffphottool.h"
+
+static bool definerunMode(pxConfig *config);
+static bool updaterunMode(pxConfig *config);
+static bool inputMode(pxConfig *config);
+static bool pendingMode(pxConfig *config);
+static bool doneMode(pxConfig *config);
+static bool advanceMode(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 = diffphottoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(DIFFPHOTTOOL_MODE_DEFINERUN, definerunMode);
+        MODECASE(DIFFPHOTTOOL_MODE_UPDATERUN, updaterunMode);
+        MODECASE(DIFFPHOTTOOL_MODE_INPUT,     inputMode);
+        MODECASE(DIFFPHOTTOOL_MODE_PENDING,   pendingMode);
+        MODECASE(DIFFPHOTTOOL_MODE_DONE,      doneMode);
+        MODECASE(DIFFPHOTTOOL_MODE_ADVANCE,   advanceMode);
+        MODECASE(DIFFPHOTTOOL_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 definerunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // Options
+    PXOPT_LOOKUP_STR(set_workdir, config->args, "-set_workdir", true, false);
+    PXOPT_LOOKUP_STR(set_label, config->args, "-set_label", false, false);
+    PXOPT_LOOKUP_STR(set_data_group, config->args, "-set_data_group", false, false);
+    PXOPT_LOOKUP_STR(set_reduction, config->args, "-set_reduction", false, false);
+    PXOPT_LOOKUP_STR(set_note, config->args, "-set_note", false, false);
+    PXOPT_LOOKUP_TIME(registered, config->args, "-set_registered", false, false);
+    PXOPT_LOOKUP_BOOL(pretend, config->args, "-pretend", false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    // Selections
+    psMetadata *where = psMetadataAlloc();
+    pxAddLabelSearchArgs(config, where, "-label", "diffRun.label", "LIKE");
+    pxAddLabelSearchArgs(config, where, "-data_group", "diffRun.data_group", "LIKE");
+    PXOPT_COPY_STR(config->args, where, "-comment", "rawExp.comment", "==");
+    PXOPT_COPY_STR(config->args, where, "-filter", "rawExp.filter", "==");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_begin", "rawExp.dateobs", ">=");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_end", "rawExp.dateobs", "<=");
+
+
+    psString query = pxDataGet("diffphottool_definerun.sql");
+    if (!query) {
+        psError(psErrorCodeLast(), false, "failed to retreive SQL statement");
+        psFree(where);
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString clause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, "\nAND %s", clause);
+        psFree(clause);
+    }
+    psFree(where);
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to run query: %s", query);
+        psFree(where);
+        return false;
+    }
+    psFree(where);
+
+    psArray *results = p_psDBFetchResult(config->dbh); // Results of query
+    if (!results) {
+        psError(psErrorCodeLast(), false, "database error");
+        return false;
+    }
+
+    if (!psArrayLength(results)) {
+        psTrace("diffphottool", 1, "no rows found");
+        psFree(results);
+        return true;
+    }
+
+    if (pretend) {
+        if (!ippdbPrintMetadatas(stdout, results, "diffPhotRun", !simple)) {
+            psError(psErrorCodeLast(), false, "failed to print array");
+            psFree(results);
+            return false;
+        }
+        psFree(results);
+        return true;
+    }
+
+    for (int i = 0; i < results->n; i++) {
+        psMetadata *row = results->data[i]; // Output row from query
+        bool mdok;                          // Status of MD lookup
+        psS64 diff_id = psMetadataLookupS64(&mdok, row, "diff_id");
+        const char *workdir = psMetadataLookupStr(&mdok, row, "workdir");
+        const char *label = psMetadataLookupStr(&mdok, row, "data_group");
+        const char *data_group = psMetadataLookupStr(&mdok, row, "data_group");
+        const char *reduction = psMetadataLookupStr(&mdok, row, "reduction");
+        const char *note = psMetadataLookupStr(&mdok, row, "note");
+
+        diffPhotRunRow *run = diffPhotRunRowAlloc(0, diff_id, "new",
+                                                  workdir ? workdir : set_workdir,
+                                                  set_label ? set_label : label,
+                                                  set_data_group ? set_data_group : data_group,
+                                                  set_reduction ? set_reduction : reduction,
+                                                  registered,
+                                                  set_note ? set_note : note);
+        if (!diffPhotRunInsertObject(config->dbh, run)) {
+            psError(psErrorCodeLast(), false, "database error");
+            if (!psDBRollback(config->dbh)) {
+                psError(psErrorCodeLast(), false, "database error");
+            }
+            psFree(run);
+            psFree(results);
+            return false;
+        }
+
+        run->diff_phot_id = psDBLastInsertID(config->dbh);
+
+        if (!diffPhotRunPrintObject(stdout, run, !simple)) {
+            psError(psErrorCodeLast(), false, "failed to print object");
+            psFree(run);
+            psFree(results);
+            return false;
+        }
+        psFree(run);
+    }
+    psFree(results);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(psErrorCodeLast(), false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool updaterunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+
+    PXOPT_COPY_S64(config->args, where, "-diff_phot_id", "diff_phot_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-label", "label", "LIKE");
+    PXOPT_COPY_STR(config->args, where, "-data_group", "data_group", "LIKE");
+    PXOPT_COPY_STR(config->args, where, "-state", "state", "==");
+    if (!psListLength(where->list)) {
+        psFree(where);
+        psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required");
+        return false;
+    }
+
+    psString query = psStringCopy("UPDATE diffPhotRun");
+
+    // pxUpdateRun gets parameters from config->args and updates
+    bool result = pxUpdateRun(config, where, &query, "diffPhotRun", "diff_phot_id", "diffPhotSkyfile", true);
+
+    psFree(query);
+    psFree(where);
+
+    return result;
+}
+
+
+static bool inputMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+
+    PXOPT_COPY_S64(config->args, where,  "-diff_phot_id", "diff_phot_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-skycell_id", "skycell_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("diffphottool_input.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString clause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, "\nWHERE %s", clause);
+        psFree(clause);
+    }
+    psFree(where);
+
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, "\n%s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(psErrorCodeLast(), false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(psErrorCodeLast(), false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("diffphottool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (!ippdbPrintMetadatas(stdout, output, "diffSkyfile", !simple)) {
+        psError(psErrorCodeLast(), false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+    psFree(output);
+
+    return true;
+}
+
+
+static bool pendingMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where,  "-diff_phot_id", "diff_phot_id", "==");
+    pxAddLabelSearchArgs(config, where, "-label", "label", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("diffphottool_pending.sql");
+    if (!query) {
+        psError(psErrorCodeLast(), false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString clause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, "\nAND %s", clause);
+        psFree(clause);
+    }
+    psFree(where);
+
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, "\n%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) {
+        psError(psErrorCodeLast(), false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("diffphottool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (!ippdbPrintMetadatas(stdout, output, "diffPhotRun", !simple)) {
+        psError(psErrorCodeLast(), false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+    psFree(output);
+
+    return true;
+}
+
+
+static bool doneMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_S64(diff_phot_id, config->args, "-diff_phot_id", true, false); // required
+    PXOPT_LOOKUP_STR(skycell_id, config->args, "-skycell_id", true, false);
+    PXOPT_LOOKUP_S16(fault, config->args, "-fault", false, false);
+    PXOPT_LOOKUP_S16(quality, config->args, "-quality", false, false);
+    PXOPT_LOOKUP_STR(path_base, config->args, "-path_base", true, false);
+    PXOPT_LOOKUP_STR(hostname, config->args, "-hostname", true, false);
+    PXOPT_LOOKUP_F32(dtime_script, config->args, "-dtime_script", false, false);
+    PXOPT_LOOKUP_STR(ver_pslib, config->args, "-ver_pslib", false, false);
+    PXOPT_LOOKUP_STR(ver_psmodules, config->args, "-ver_psmodules", false, false);
+    PXOPT_LOOKUP_STR(ver_ppstats, config->args, "-ver_ppstats", false, false);
+    PXOPT_LOOKUP_STR(ver_psphot, config->args, "-ver_psphot", false, false);
+
+    psString version = pxMergeCodeVersions(ver_pslib, ver_psmodules);
+    version = pxMergeCodeVersions(version, ver_ppstats);
+    version = pxMergeCodeVersions(version, ver_psphot);
+
+    if (!diffPhotSkyfileInsert(config->dbh, diff_phot_id, skycell_id, path_base, dtime_script, hostname,
+                               fault, quality, version)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool advanceMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where,  "-diff_phot_id", "diff_phot_id", "==");
+    pxAddLabelSearchArgs (config, where, "-label", "label", "==");
+
+    psString query = pxDataGet("diffphottool_advance.sql");
+    if (!query) {
+        psError(psErrorCodeLast(), false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    psString whereClause = psStringCopy("");
+    if (psListLength(where->list)) {
+        psString clause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&whereClause, "\nAND %s", clause);
+        psFree(clause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQueryF(config->dbh, query, whereClause)) {
+        psError(psErrorCodeLast(), false, "database error");
+        psFree(query);
+        psFree(whereClause);
+        return false;
+    }
+    psFree(query);
+    psFree(whereClause)
+
+    return true;
+}
+
+
+static bool revertMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where,  "-diff_phot_id", "diff_phot_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-skycell_id", "skycell_id", "==");
+    PXOPT_COPY_S16(config->args, where, "-fault", "fault", "==");
+    pxAddLabelSearchArgs (config, where, "-label", "label", "LIKE");
+
+    PXOPT_LOOKUP_BOOL(all, config->args, "-all", false);
+    if (!psListLength(where->list) && !all) {
+        psError(PXTOOLS_ERR_CONFIG, true, "search parameters or -all are required");
+        psFree(where);
+        return false;
+    }
+
+    psString query = pxDataGet("diffphottool_revert.sql");
+    if (!query) {
+        psError(psErrorCodeLast(), false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString clause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, "\nAND %s", clause);
+        psFree(clause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    return true;
+}
+
Index: /trunk/ippTools/src/diffphottool.h
===================================================================
--- /trunk/ippTools/src/diffphottool.h	(revision 28343)
+++ /trunk/ippTools/src/diffphottool.h	(revision 28343)
@@ -0,0 +1,38 @@
+/*
+ * diffphottool.h
+ *
+ * Copyright (C) 2007-2010  Joshua Hoblitt, Paul Price
+ *
+ * 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 DIFFPHOTTOOL_H
+#define DIFFPHOTTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    DIFFPHOTTOOL_MODE_NONE           = 0x0,
+    DIFFPHOTTOOL_MODE_DEFINERUN,
+    DIFFPHOTTOOL_MODE_UPDATERUN,
+    DIFFPHOTTOOL_MODE_INPUT,
+    DIFFPHOTTOOL_MODE_PENDING,
+    DIFFPHOTTOOL_MODE_DONE,
+    DIFFPHOTTOOL_MODE_ADVANCE,
+    DIFFPHOTTOOL_MODE_REVERT,
+} diffphottoolMode;
+
+pxConfig *diffphottoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // DIFFPHOTTOOL_H
Index: /trunk/ippTools/src/diffphottoolConfig.c
===================================================================
--- /trunk/ippTools/src/diffphottoolConfig.c	(revision 28343)
+++ /trunk/ippTools/src/diffphottoolConfig.c	(revision 28343)
@@ -0,0 +1,151 @@
+/*
+ * diffphottoolConfig.c
+ *
+ * Copyright (C) 2007-2010  Joshua Hoblitt, Paul Price
+ *
+ * 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 "diffphottool.h"
+
+pxConfig *diffphottoolConfig(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(psErrorCodeLast(), false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    psTime *now = psTimeGetNow(PS_TIME_TAI);
+
+    // -definerun
+    psMetadata *definerunArgs = psMetadataAlloc();
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-set_workdir", 0, "define workdir (required)", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-set_label", 0, "define label", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-set_reduction", 0, "define reduction class", NULL);
+    psMetadataAddTime(definerunArgs, PS_LIST_TAIL, "-set_registered", 0, "time detrend run was registered", now);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-set_data_group", 0, "define data group", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-set_note", 0, "define note", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-label", PS_META_DUPLICATE_OK, "search by warpRun label (LIKE comparison, multiple OK)", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-data_group", 0, "search by warpRun data_group", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-comment", 0, "search for comment (LIKE)", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-filter", 0, "search for filter", NULL);
+    psMetadataAddTime(definerunArgs, PS_LIST_TAIL, "-dateobs_begin", 0, "search for exposures by time (>=)", NULL);
+    psMetadataAddTime(definerunArgs, PS_LIST_TAIL, "-dateobs_end", 0, "search for exposures by time (<)", NULL);
+    psMetadataAddBool(definerunArgs, PS_LIST_TAIL, "-pretend",  0, "do not actually modify the database", false);
+    psMetadataAddBool(definerunArgs, PS_LIST_TAIL, "-simple", 0, "use the simple output format", false);
+
+    // -updaterun
+    psMetadata *updaterunArgs = psMetadataAlloc();
+    psMetadataAddS64(updaterunArgs, PS_LIST_TAIL, "-diff_phot_id", 0, "search by diffphot ID", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-state", 0, "set state", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-label", 0, "search by label (LIKE comparison)", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-data_group", 0, "search by data_group (LIKE comparison)", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_label", 0, "define new value for label", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_state", 0, "define new state", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_data_group", 0, "define new data_group", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_note", 0, "define new note", NULL);
+
+    // -input
+    psMetadata *inputArgs = psMetadataAlloc();
+    psMetadataAddS64(inputArgs, PS_LIST_TAIL, "-diff_phot_id", 0, "search by diffphot ID", 0);
+    psMetadataAddStr(inputArgs, PS_LIST_TAIL, "-skycell_id", 0, "search by skycell ID", NULL);
+    psMetadataAddU64(inputArgs, PS_LIST_TAIL, "-limit", 0, "limit result set to N items", 0);
+    psMetadataAddBool(inputArgs, PS_LIST_TAIL, "-simple", 0, "use the simple output format", false);
+
+    // -pending
+    psMetadata *pendingArgs = psMetadataAlloc();
+    psMetadataAddS64(pendingArgs, PS_LIST_TAIL, "-diff_phot_id", 0, "search by diffphot ID", 0);
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-label", PS_META_DUPLICATE_OK, "search by label", 0);
+    psMetadataAddU64(pendingArgs, PS_LIST_TAIL, "-limit", 0, "limit result set to N items", 0);
+    psMetadataAddBool(pendingArgs, PS_LIST_TAIL, "-simple", 0, "use the simple output format", false);
+
+    // -done
+    psMetadata *doneArgs = psMetadataAlloc();
+    psMetadataAddS64(doneArgs, PS_LIST_TAIL, "-diff_phot_id", 0, "define diffphot ID (required)", 0);
+    psMetadataAddStr(doneArgs, PS_LIST_TAIL, "-skycell_id", 0, "define skycell of file (required)", NULL);
+    psMetadataAddS16(doneArgs, PS_LIST_TAIL, "-fault", 0, "set fault code", 0);
+    psMetadataAddS16(doneArgs, PS_LIST_TAIL, "-quality", 0, "set quality", 0);
+    psMetadataAddStr(doneArgs, PS_LIST_TAIL, "-path_base", 0, "define base output location (required)", NULL);
+    psMetadataAddF32(doneArgs, PS_LIST_TAIL, "-dtime_script", 0, "define elapsed time in script (seconds)", NAN);
+    psMetadataAddStr(doneArgs, PS_LIST_TAIL, "-ver_pslib", 0, "define psLib version", NULL);
+    psMetadataAddStr(doneArgs, PS_LIST_TAIL, "-ver_psmodules", 0, "define psModules version", NULL);
+    psMetadataAddStr(doneArgs, PS_LIST_TAIL, "-ver_ppstats", 0, "define ppStats version", NULL);
+    psMetadataAddStr(doneArgs, PS_LIST_TAIL, "-ver_psphot", 0, "define psphot version", NULL);
+
+    // -advance
+    psMetadata *advanceArgs = psMetadataAlloc();
+    psMetadataAddS64(advanceArgs, PS_LIST_TAIL, "-diff_phot_id", 0, "select by diffphot ID", 0);
+    psMetadataAddStr(advanceArgs, PS_LIST_TAIL, "-label", PS_META_DUPLICATE_OK, "select by label", NULL);
+    psMetadataAddS32(advanceArgs, PS_LIST_TAIL, "-limit", 0, "limit number of results", 0);
+
+    // -revert
+    psMetadata *revertArgs = psMetadataAlloc();
+    psMetadataAddS64(revertArgs, PS_LIST_TAIL, "-diff_phot_id", 0, "search by diffphot ID", 0);
+    psMetadataAddStr(revertArgs, PS_LIST_TAIL, "-skycell_id", 0, "search by skycell_id", NULL);
+    psMetadataAddStr(revertArgs, PS_LIST_TAIL, "-label", PS_META_DUPLICATE_OK, "search by label", NULL);
+    psMetadataAddS16(revertArgs, PS_LIST_TAIL, "-fault", 0, "search by fault code", 0);
+    psMetadataAddBool(revertArgs, PS_LIST_TAIL, "-all", 0, "allow no search terms", 0);
+
+    psFree(now);
+
+    psMetadata *argSets = psMetadataAlloc();
+    psMetadata *modes = psMetadataAlloc();
+
+    PXOPT_ADD_MODE("-definerun", "", DIFFPHOTTOOL_MODE_DEFINERUN, definerunArgs);
+    PXOPT_ADD_MODE("-updaterun", "", DIFFPHOTTOOL_MODE_UPDATERUN, updaterunArgs);
+    PXOPT_ADD_MODE("-input",     "", DIFFPHOTTOOL_MODE_INPUT,     inputArgs);
+    PXOPT_ADD_MODE("-pending",   "", DIFFPHOTTOOL_MODE_PENDING,   pendingArgs);
+    PXOPT_ADD_MODE("-done",      "", DIFFPHOTTOOL_MODE_DONE,      doneArgs);
+    PXOPT_ADD_MODE("-advance",   "", DIFFPHOTTOOL_MODE_ADVANCE,   advanceArgs);
+    PXOPT_ADD_MODE("-revert",    "", DIFFPHOTTOOL_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;
+}
