Index: /trunk/pstamp/src/.cvsignore
===================================================================
--- /trunk/pstamp/src/.cvsignore	(revision 16592)
+++ /trunk/pstamp/src/.cvsignore	(revision 16593)
@@ -1,3 +1,5 @@
 ppstamp
+pstampparse
+pstamprequest
 Makefile
 Makefile.in
Index: /trunk/pstamp/src/Makefile.am
===================================================================
--- /trunk/pstamp/src/Makefile.am	(revision 16592)
+++ /trunk/pstamp/src/Makefile.am	(revision 16593)
@@ -1,3 +1,3 @@
-bin_PROGRAMS = ppstamp pstamprequest pstampparse
+bin_PROGRAMS = ppstamp pstamprequest pstampparse pstampfinish
 
 noinst_HEADERS = \
@@ -7,6 +7,6 @@
 ppstamp_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) 
 pstamprequest_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) 
-#pstampparse_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS)  $(IPPDB_CFLAGS)
 pstampparse_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS)
+pstampfinish_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(IPPDB_CFLAGS)
 
 # $(PSASTRO_CFLAGS) $(PPSTATS_CFLAGS) $(ppstamp_CFLAGS)
@@ -14,6 +14,6 @@
 ppstamp_LDFLAGS = $(PSLIB_LIBS) $(PSMODULE_LIBS) 
 pstamprequest_LDFLAGS = $(PSLIB_LIBS) $(PSMODULE_LIBS) 
-#pstampparse_LDFLAGS = $(PSLIB_LIBS) $(PSMODULE_LIBS) $(IPPDB_LIBS)
-pstampparse_LDFLAGS = $(PSLIB_LIBS) $(PSMODULE_LIBS)
+pstampparse_LDFLAGS = $(PSLIB_LIBS) $(PSMODULE_LIBS) 
+pstampfinish_LDFLAGS = $(PSLIB_LIBS) $(PSMODULE_LIBS) $(IPPDB_LIBS)
 
 ppstamp_SOURCES = \
@@ -38,4 +38,8 @@
 	pstampparse.c
 
+pstampfinish_SOURCES = \
+	pstampfinish.c
+
+
 clean-local:
 	-rm -f TAGS
Index: /trunk/pstamp/src/ppstampMakeStamp.c
===================================================================
--- /trunk/pstamp/src/ppstampMakeStamp.c	(revision 16592)
+++ /trunk/pstamp/src/ppstampMakeStamp.c	(revision 16593)
@@ -426,4 +426,11 @@
     PS_ASSERT_PTR_NON_NULL(hdu->header, 1)
 
+    // for some reason these are getting initialized to garbag
+    input->fpa->fromTPA = 0;
+    input->fpa->toTPA = 0;
+    input->fpa->toSky = 0;
+    chip->toFPA = 0;
+    chip->fromFPA = 0;
+
     if (!pmAstromReadWCS(input->fpa, chip, hdu->header, 1.0)) {
         // we can live without WCS if the ROI is specified in pixels
Index: /trunk/pstamp/src/ppstampParseCamera.c
===================================================================
--- /trunk/pstamp/src/ppstampParseCamera.c	(revision 16592)
+++ /trunk/pstamp/src/ppstampParseCamera.c	(revision 16593)
@@ -55,4 +55,5 @@
     }
 
+    // TODO: only do the workaround if the file level is chip, skycells should be fine
     if (!strcmp(config->cameraName, "MEGACAM")) {
         // workaround bug 986 and related
Index: /trunk/pstamp/src/pstamp.h
===================================================================
--- /trunk/pstamp/src/pstamp.h	(revision 16592)
+++ /trunk/pstamp/src/pstamp.h	(revision 16593)
@@ -36,3 +36,6 @@
 #define STAMP_REQUEST_VERSION "1.0"
 
+#define STAMP_RESULTS_EXTNAME "PS1_PS_RESULTS"
+#define STAMP_RESULTS_VERSION "1.0"
+
 #endif
Index: /trunk/pstamp/src/pstampfinish.c
===================================================================
--- /trunk/pstamp/src/pstampfinish.c	(revision 16593)
+++ /trunk/pstamp/src/pstampfinish.c	(revision 16593)
@@ -0,0 +1,371 @@
+// pstamprequestfinish  - Look for postage stamp requests that have completed and
+// create the results file
+
+#include <string.h>
+#include <libgen.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include <ippdb.h>
+#include "pstamp.h"
+#include "pstampROI.h"
+
+// XXX what do I have in mind for the list mode?
+typedef enum {
+    PSRF_MODE_UNKNOWN,
+    PSRF_MODE_FINISH,
+    PSRF_MODE_LIST
+} psrfMode;
+
+typedef struct {
+    pmConfig    *config;
+    psMetadata  *md;
+    psrfMode     mode;
+    bool        verbose;
+    bool        simple;
+    int         limit;
+    psString    dsRoot;
+    psString    dsProduct;
+    int         exitStatus;
+} psrfOptions;
+
+
+#define RESULTS_FILE_NAME "results.fits"
+
+
+static void usage()
+{
+    // XXX: fill this out
+    fprintf(stderr, "argument error\n");
+    exit(PSTAMP_ERR_ARGUMENTS);
+}
+
+static psrfMode parseMode(char *modeString)
+{
+    psrfMode mode = PSRF_MODE_UNKNOWN;
+
+    if (!strcasecmp(modeString, "finish")) {
+        mode = PSRF_MODE_FINISH;
+    } else if (!strcasecmp(modeString, "list")) {
+        mode = PSRF_MODE_LIST;
+    } else {
+        fprintf(stderr, "unknown command mode: %s\n", modeString);
+        usage();
+    }
+    return mode;
+}
+
+
+static psrfOptions *parseArguments(int argc, char *argv[])
+{
+    int argnum;
+    psrfOptions *options = psAlloc(sizeof(psrfOptions));
+
+    memset(options, 0, sizeof(*options));
+
+    options->config = pmConfigRead(&argc, argv, NULL);
+    if (!options->config) {
+        psError(PS_ERR_UNKNOWN, false, "cannot find site file");
+        psFree(options);
+        return NULL;
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-mode"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        if (argnum == argc) {
+            fprintf(stderr, "value required for mode\n");
+            usage();
+        }
+        options->mode = parseMode(argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-verbose"))) {
+        options->verbose = true;
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-simple"))) {
+        options->simple = true;
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-limit"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        if (argnum == argc) {
+            fprintf(stderr, "value required for limit\n");
+            usage();
+        }
+        options->limit = atoi(argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    if (argc != 1) {
+        fprintf(stderr, "unknown arguments supplied:");
+        fprintf(stderr, " %s", argv[1]);
+        for (int i=2; i<argc; i++) {
+            fprintf(stderr, ", %s", argv[i]);
+        }
+        fprintf(stderr, "\n");
+        usage();
+    }
+
+    return options;
+}
+
+static bool setupDB(psrfOptions *options)
+{
+    // XXX: need to select the database name based on the project name specified in the input
+
+    options->config->database = pmConfigDB(options->config);
+
+    if (!options->config->database) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        return false;
+    }
+
+    return true;
+}
+
+static bool writeTable(psString fileName, psArray *table)
+{
+    psFits *fitsFile = psFitsOpen(fileName, "w");
+    if (fitsFile == NULL) {
+        psError(PS_ERR_IO, true, "failed to open %s for output\n", fileName);
+        return false;
+    }
+
+    if (!psFitsWriteTable(fitsFile, NULL, table, STAMP_RESULTS_EXTNAME)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to write fits table");
+        return false;
+    }
+
+    if (!psFitsClose(fitsFile)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to close fits file");
+        return false;
+    }
+
+    return true;
+}
+
+static psString getBaseName(psString path, bool addDotFits)
+{
+    psString str = psStringCopy(path);
+
+    psString base = basename(str);
+
+    if (addDotFits) {
+        psString newBase = NULL;
+        psStringAppend(&newBase, "%s.fits", base);
+        base = newBase;
+    }
+    return base;
+}
+
+static bool setRequestStopped(psrfOptions *options, pstampRequestRow *pReq)
+{
+    psString query = "UPDATE pstampRequest SET state = 'stop' WHERE req_id = %" PRId64;
+
+    if (!p_psDBRunQuery(options->config->database, query, pReq->req_id)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    psU64 affected = psDBAffectedRows(options->config->database);
+    if (affected != 1) {
+        psError(PS_ERR_UNKNOWN, false, "should have affected one row but %" 
+                                        PRIu64 " rows were modified", affected);
+        return false;
+    }
+
+    return true;
+
+}
+
+static bool finishRequest(psrfOptions *options, pstampRequestRow *pReq, psArray *stoppedJobs)
+{
+    // build results file in dsRoot/pReq->outFileset
+    psString filesetDir = NULL;
+    psStringAppend(&filesetDir, "%s/%s", options->dsRoot, pReq->outFileset);
+
+    psString resultsFile = NULL;
+    psStringAppend(&resultsFile, "%s/%s", filesetDir, RESULTS_FILE_NAME);
+
+    if (options->verbose) {
+        fprintf(stderr, "results file: %s\n", resultsFile);
+    }
+
+    // loop over jobs and find the list of job result and stamp files
+    // add the data to a list.
+
+
+    psArray *table = psArrayAlloc(0);
+    psArray *stamps = psArrayAlloc(0);
+
+    long numJobs = psArrayLength(stoppedJobs);
+    long numStamps = 0;
+    for (int i = 0; i < numJobs; i++) {
+        pstampJobRow *pJob = psArrayGet(stoppedJobs, i);
+
+        psMetadata *jobmd = psMetadataAlloc();
+        psArrayAdd(table, 1, jobmd);
+
+        psMetadataAddS32(jobmd, PS_LIST_TAIL, "RESULT", 0, "", pJob->result);
+
+        if (pJob->result == 0) {
+            ++numStamps;
+            // XXX: should we check for existence?
+            psString stampName = getBaseName(pJob->outputBase, true);
+            psString inputName = getBaseName(pJob->uri, false);
+            psArrayAdd(stamps, 1, stampName);
+            // add basename of stamp and output uri
+            psMetadataAddStr(jobmd, PS_LIST_TAIL, "STAMP_NAME", PS_DATA_STRING, "", stampName);
+            psMetadataAddStr(jobmd, PS_LIST_TAIL, "INPUT_NAME", PS_DATA_STRING, "", inputName);
+            psMetadataAddStr(jobmd, PS_LIST_TAIL, "COMMENT", PS_DATA_STRING, "",
+                "stamp made successfully");
+        } else {
+            // TODO: convert the result code into something useful to the user
+            psMetadataAddStr(jobmd, PS_LIST_TAIL, "COMMENT", PS_DATA_STRING, "",
+                "add description of what happened here");
+        }
+    }
+
+    if (!writeTable(resultsFile, table)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to write pstamp request results to %s", resultsFile);
+        return false;
+    }
+    
+    psString fileset_id = getBaseName(pReq->outFileset, false);
+    psString command = NULL;
+
+    psStringAppend(&command, "dsreg -dsdir %s -add -type PSTAMP %s %s", 
+            options->dsRoot, options->dsProduct, fileset_id);
+
+    printf("command is: %s\n", command);
+
+    // open a pipe to dsreg and set up the output fileset
+    // Each line of input describes one of the files
+    #define DSREG_FORMAT_STRING "%s chip na\n"
+
+    FILE *pipe = popen(command, "w");
+
+    if (pipe == NULL) {
+        psError(PS_ERR_UNKNOWN, true, "failed to open pipe to dsreg");
+        return false;
+    }
+
+    // First the results table that we built above
+    fprintf(pipe, DSREG_FORMAT_STRING, "results.fits");
+
+    // Next each of the stamps
+    for (int i = 0; i < numStamps; i++) {
+        fprintf(pipe, DSREG_FORMAT_STRING, (psString) psArrayGet(stamps, i));
+    }
+
+    int status = pclose(pipe);
+    if (status) {
+        psError(PS_ERR_UNKNOWN, true, "dsreg failed with status: %d", status);
+        return false;
+    }
+    // update the database setting the request state to stop
+    return setRequestStopped(options, pReq);
+}
+
+int main(int argc, char *argv[])
+{
+    psrfOptions *options =  parseArguments(argc, argv);
+    int i;
+    if (!options) {
+        psErrorStackPrint(stderr, "Unable to ParseArguments");
+        return PS_EXIT_UNKNOWN_ERROR;
+    }
+
+    options->dsRoot = psMetadataLookupStr(NULL, options->config->site, "PSTAMP_DATA_STORE_ROOT");
+    if (options->dsRoot == NULL) {
+        fprintf(stderr, "failed to find PSTAMP_DS_ROOT in site configuration\n");
+        exit (PS_EXIT_CONFIG_ERROR);
+    }
+    options->dsProduct = psMetadataLookupStr(NULL, options->config->site, "PSTAMP_DATA_STORE_PRODUCT");
+    if (options->dsProduct == NULL) {
+        fprintf(stderr, "failed to find PSTAMP_DATA_STORE_PRODUCT in site configuration\n");
+        exit (PS_EXIT_CONFIG_ERROR);
+    }
+    if (options->verbose) {
+        fprintf(stderr, "data store root: %s\n", options->dsRoot);
+    }
+
+    if (!setupDB(options)) {
+        return PS_EXIT_CONFIG_ERROR;
+    }
+    psDB *dbh =  options->config->database;
+
+    psMetadata *whereRunning = psMetadataAlloc();
+    psMetadataAddStr(whereRunning, PS_LIST_TAIL, "state", PS_META_DEFAULT, "==", "run");
+
+    psMetadata *whereStopped = psMetadataAlloc();
+    psMetadataAddStr(whereStopped, PS_LIST_TAIL, "state", PS_META_DEFAULT, "==", "stop");
+
+    // get all requests in state run
+    psArray *runningRequests = pstampRequestSelectRowObjects(dbh, whereRunning, options->limit);
+
+    if (!runningRequests || (psArrayLength(runningRequests) == 0)) {
+        if (options->verbose) {
+            fprintf(stderr, "no pstampRequests with state == run\n");
+        }
+        exit (PS_EXIT_SUCCESS);
+    }
+
+    int numRequests = psArrayLength(runningRequests);
+
+    for (i=0; i<numRequests; i++) {
+        pstampRequestRow *pReq = (pstampRequestRow *) psArrayGet(runningRequests, i);
+
+        if (options->verbose) {
+            pstampRequestPrintObject(stderr, pReq, false);
+        }
+
+        psMetadataAddS64(whereRunning, PS_LIST_TAIL, "req_id", PS_META_REPLACE, "==", pReq->req_id);
+
+        psArray *runningJobs = pstampJobSelectRowObjects(dbh, whereRunning, 0);
+        long numJobs = psArrayLength(runningJobs);
+
+        if (numJobs != 0) {
+            if (options->verbose) {
+                fprintf(stderr, "request %" PRId64 " has % " PRId64 " running jobs\n", pReq->req_id,
+                    numJobs);
+                psFree(runningJobs);
+            }
+            continue;
+        }
+
+        psMetadataAddS64(whereStopped, PS_LIST_TAIL, "req_id", PS_META_REPLACE, "==", pReq->req_id);
+
+        psArray *stoppedJobs = pstampJobSelectRowObjects(dbh, whereStopped, 0);
+        if (stoppedJobs) {
+            numJobs = psArrayLength(stoppedJobs);
+
+            if (numJobs == 0) {
+                if (options->verbose) {
+                    fprintf(stderr, "request %" PRId64 " has no stopped jobs\n", pReq->req_id);
+                    psFree(stoppedJobs);
+                    stoppedJobs = NULL;
+                    // fall through, finishRequest knows what to do with null jobs array
+                }
+            }
+        }
+
+        if (options->verbose) {
+            fprintf(stderr, "request %" PRId64 " has % " PRId64 " stopped jobs\n", pReq->req_id, numJobs);
+            pstampJobPrintObjects(stderr, stoppedJobs, false);
+        }
+
+        if (!finishRequest(options, pReq, stoppedJobs)) {
+            psErrorStackPrint(stderr, "Unable to finish request: %" PRId64, pReq->req_id);
+            exit(PS_EXIT_UNKNOWN_ERROR);
+        }
+        psFree(stoppedJobs);
+    }
+
+    psFree(runningRequests);
+
+    return 0;
+}
Index: /trunk/pstamp/src/pstampparse.c
===================================================================
--- /trunk/pstamp/src/pstampparse.c	(revision 16592)
+++ /trunk/pstamp/src/pstampparse.c	(revision 16593)
@@ -11,4 +11,5 @@
     pmConfig    *config;
     psString    fileName;
+    psString    outputDirectory;
     psMetadata  *md;
     psString    roiString;
@@ -88,4 +89,13 @@
     if ((argnum = psArgumentGet(argc, argv, "-simple"))) {
         options->simple = true;
+        psArgumentRemove(argnum, &argc, argv);
+    }
+    if ((argnum = psArgumentGet(argc, argv, "-out_dir"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        if (argnum == argc) {
+            fprintf(stderr, "value required for out_dir\n");
+            usage();
+        }
+        options->outputDirectory = argv[argnum];
         psArgumentRemove(argnum, &argc, argv);
     }
@@ -538,5 +548,5 @@
 static psArray * parseByCoord(pspOptions *options)
 {
-    psError(PS_ERR_UNKNOWN, true, "REQ_TYPE: bycoord not implemented yet");
+    psError(PSTAMP_ERR_NOT_IMPLEMENTED, true, "REQ_TYPE: bycoord not implemented yet");
     return NULL;
 }
@@ -591,6 +601,12 @@
     psString cmd = NULL;
 
-    psStringAppend(&cmd, "pstamptool -addjob -req_id %ld -uri %s -outputBase %s -args '%s' >/dev/null",
-                                options->req_id, uri, outputBase, commandArgs);
+    psStringAppend(&cmd, "pstamptool -addjob -req_id %ld -uri %s -outputBase ", options->req_id, uri);
+    
+    if (options->outputDirectory) {
+        psStringAppend(&cmd, "%s/", options->outputDirectory);
+    }
+    
+    psStringAppend(&cmd, "%s -args '%s' >/dev/null", outputBase, commandArgs);
+
     if (options->verbose) {
         fprintf(stderr, "excuting system(%s)\n", cmd);
Index: /trunk/pstamp/src/pstamprequest.c
===================================================================
--- /trunk/pstamp/src/pstamprequest.c	(revision 16592)
+++ /trunk/pstamp/src/pstamprequest.c	(revision 16593)
@@ -371,7 +371,13 @@
     table->data[0] = options->md;
 
-    psFitsWriteTable(fitsFile, NULL, table, STAMP_REQUEST_EXTNAME);
-
-    psFitsClose(fitsFile);
+    if (!psFitsWriteTable(fitsFile, NULL, table, STAMP_REQUEST_EXTNAME)) {
+        psError(PS_ERR_IO, false, "failed to write fits table");
+        return false;
+    }
+
+    if (!  psFitsClose(fitsFile)) {
+        psError(PS_ERR_IO, false, "failed to close fits table");
+        return false;
+    }
 
     return true;
@@ -415,9 +421,10 @@
     }
 
-//    psMetadataPrint(stderr, options->md, 0);
+    // psMetadataPrint(stderr, options->md, 0);
 
     if (writeTable(options)) {
         return 0;
     } else {
+        psErrorStackPrint(stderr, "failed to create request table");
         // XXX: we should have variable status codes that indicate what the problem was
         return 1;
