Index: trunk/psModules/src/config/pmConfig.c
===================================================================
--- trunk/psModules/src/config/pmConfig.c	(revision 6418)
+++ trunk/psModules/src/config/pmConfig.c	(revision 6873)
@@ -3,6 +3,6 @@
  *  @author PAP, IfA
  *
- *  @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2006-02-10 02:43:19 $
+ *  @version $Revision: 1.10 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:10:08 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -11,9 +11,49 @@
 #include <stdio.h>
 #include <string.h>
+#include <unistd.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <glob.h>
 #include "pslib.h"
 #include "pmConfig.h"
 
-#define PS_SITE "PS_SITE"               // Name of the environment variable containing the site config file
-#define PS_DEFAULT_SITE "ipprc.config"  // Default site config file
+#define PS_SITE "PS_SITE"         // Name of the environment variable containing the site config file
+#define PS_DEFAULT_SITE ".ipprc"  // Default site config file
+
+static psArray *configPath = NULL;
+
+static void configFree(pmConfig *config)
+{
+    psFree(config->site);
+    psFree(config->files);
+    psFree(config->camera);
+    psFree(config->recipes);
+    psFree(config->arguments);
+    psFree(config->database);
+}
+
+pmConfig *pmConfigAlloc(void)
+{
+    pmConfig *config = psAlloc(sizeof(pmConfig));
+    (void)psMemSetDeallocator(config, (psFreeFunc)configFree);
+
+    // Initialise
+    config->site = NULL;
+    config->camera = NULL;
+    config->recipes = NULL;
+    config->arguments = NULL;
+    config->database = NULL;
+
+    // the file structure is used to carry pmFPAfiles
+    config->files = psMetadataAlloc ();
+    return config;
+}
+
+void pmConfigDone(void)
+{
+    psFree(configPath);
+}
+
 
 /** readConfig
@@ -23,24 +63,74 @@
  *
  */
-static bool readConfig(
+bool readConfig(
     psMetadata **config,                // Config to output
     const char *name,                   // Name of file
     const char *description)            // Description of file
 {
+    char *realName = NULL;
     unsigned int numBadLines = 0;
+    struct stat filestat;
 
     psLogMsg(__func__, PS_LOG_INFO, "Loading %s configuration from file %s\n",
              description, name);
-    *config = psMetadataConfigParse(NULL, &numBadLines, name, true);
+
+    uid_t uid = getuid();
+    gid_t gid = getgid();
+
+    // we try: name, path[0]/name, path[1]/name, ...
+    // find the first existing entry in the path (starting with the bare name)
+    realName = psStringCopy (name);
+    psTrace (__func__, 5, "trying %s\n", realName);
+
+    int status = stat (realName, &filestat);
+    if (status == 0) {
+        if ((uid == filestat.st_uid) && (filestat.st_mode & S_IRUSR))
+            goto found;
+        if ((gid == filestat.st_gid) && (filestat.st_mode & S_IRGRP))
+            goto found;
+        if (filestat.st_mode & S_IROTH)
+            goto found;
+    }
+    psFree (realName);
+
+    if (configPath == NULL) {
+        psError(PS_ERR_IO, false, "Cannot find %s configuration file in path\n", description);
+        return false;
+    }
+
+    for (int i = 0; i < configPath->n; i++) {
+        realName = psStringCopy (configPath->data[i]);
+        psStringAppend (&realName, "/%s", name);
+        psTrace (__func__, 5, "trying %s\n", realName);
+
+        status = stat (realName, &filestat);
+        if (status == 0) {
+            if ((uid == filestat.st_uid) && (filestat.st_mode & S_IRUSR))
+                goto found;
+            if ((gid == filestat.st_gid) && (filestat.st_mode & S_IRGRP))
+                goto found;
+            if (filestat.st_mode & S_IROTH)
+                goto found;
+        }
+        psFree (realName);
+    }
+
+    psError(PS_ERR_IO, false, "Cannot find %s configuration file in path\n", description);
+    return false;
+
+found:
+    *config = psMetadataConfigParse(NULL, &numBadLines, realName, true);
     if (numBadLines > 0) {
         psLogMsg(__func__, PS_LOG_WARN, "%d bad lines in %s configuration file (%s)\n",
-                 description, name);
+                 description, realName);
     }
     if (!*config) {
         psError(PS_ERR_IO, false, "Unable to read %s configuration from %s\n",
-                description, name);
+                description, realName);
+        psFree (realName);
         return false;
     }
 
+    psFree (realName);
     return true;
 }
@@ -57,23 +147,13 @@
  line.
  *****************************************************************************/
-bool pmConfigRead(
-    psMetadata **site,
-    psMetadata **camera,
-    psMetadata **recipe,
+pmConfig *pmConfigRead(
     int *argc,
-    char **argv,
-    const char *recipeName)
-{
-    /* XXX: We need clarification on what is to be done if these arguments are
-    NULL.
-        PS_ASSERT_PTR_NON_NULL(site, false);
-        PS_ASSERT_PTR_NON_NULL(*site, false);
-        PS_ASSERT_PTR_NON_NULL(camera, false);
-        PS_ASSERT_PTR_NON_NULL(*camera, false);
-        PS_ASSERT_PTR_NON_NULL(recipe, false);
-        PS_ASSERT_PTR_NON_NULL(*recipe, false);
-        PS_ASSERT_INT_POSITIVE(*argc, false);
-        PS_ASSERT_PTR_NON_NULL(argv, false);
-    */
+    char **argv)
+{
+    PS_ASSERT_INT_POSITIVE(*argc, false);
+    PS_ASSERT_PTR_NON_NULL(argv, false);
+
+    pmConfig *config = pmConfigAlloc(); // The configuration, containing site, camera and recipes
+
     //
     // The following section of code attempts to determine which file is
@@ -97,5 +177,5 @@
                      "-site command-line switch provided without the required filename --- ignored.\n");
         } else {
-            siteName = argv[argNum];
+            siteName = psStringCopy(argv[argNum]);
             psArgumentRemove(argNum, argc, argv);
         }
@@ -106,4 +186,7 @@
     if (!siteName) {
         siteName = getenv(PS_SITE);
+        if (siteName) {
+            siteName = psStringCopy (siteName);
+        }
     }
 
@@ -111,46 +194,33 @@
     // Last chance is ~/.ipprc
     //
-    bool cleanupSiteName = false; // Do I have to psFree siteName?
     if (!siteName) {
-        siteName = psStringCopy(PS_DEFAULT_SITE);
-        cleanupSiteName = true;
-    }
-
-    //
-    // We have the connfiguration filename; now we read and parse the config
+        char *home = getenv("HOME");
+        siteName = psStringCopy(home);
+        psStringAppend(&siteName, "/%s", PS_DEFAULT_SITE);
+    }
+
+
+    // We have the configuration filename; now we read and parse the config
     // file and store in psMetadata struct site.
     //
 
-    if (!readConfig(site, siteName, "site")) {
-        return false;
-    }
-    if (cleanupSiteName) {
-        psFree(siteName);
-    }
-
-
-    //
-    // Next, we do a similar thing for the recipe configuration file.  The
-    // file is read and parsed into psMetadata struct "recipe".
-    //
-    //
-    argNum = psArgumentGet(*argc, argv, "-recipe");
-    if (argNum > 0) {
-        psArgumentRemove(argNum, argc, argv);
-        if (argNum >= *argc) {
-            psLogMsg(__func__, PS_LOG_WARN,
-                     "-recipe command-line switch provided without the required filename --- ignored.\n");
-        } else {
-            psArgumentRemove(argNum, argc, argv);
-            readConfig(recipe, argv[argNum], "recipe");
-        }
-    }
-    // Or, load the recipe from the camera file, if appropriate
-    if (! *recipe && *camera && recipeName) {
-        *recipe = pmConfigRecipeFromCamera(*camera, recipeName);
-    }
-
-
-    //
+    if (!readConfig(&config->site, siteName, "site")) {
+        psFree(config);
+        return NULL;
+    }
+    psFree(siteName);
+
+    // define the config-file search path (configPath)
+    if (configPath) {
+        psFree(configPath);
+        configPath = NULL;
+    }
+    char *path = psMetadataLookupStr(NULL, config->site, "PATH");
+    if (path) {
+        psList *list = psStringSplit(path, ":");
+        configPath = psListToArray(list);
+        psFree(list);
+    }
+
     // Next, we do a similar thing for the camera configuration file.  The
     // file is read and parsed into psMetadata struct "camera".
@@ -164,9 +234,11 @@
         } else {
             psArgumentRemove(argNum, argc, argv);
-            readConfig(camera, argv[argNum], "camera");
-        }
-    } else {
-        // XXX: Not sure is this is correct.
-        *camera = NULL;
+            readConfig(&config->camera, argv[argNum], "camera");
+        }
+    }
+
+    // Load the recipes from the camera file, if appropriate
+    if (! config->recipes && config->camera) {
+        pmConfigReadRecipes(config);
     }
 
@@ -183,5 +255,5 @@
     // with a call to psTimeInitialize.
     //
-    psString timeName = psMetadataLookupStr(&mdok, *site, "TIME");
+    psString timeName = psMetadataLookupStr(&mdok, config->site, "TIME");
     if (mdok && timeName) {
         psTrace(__func__, 7, "Initialising psTime with file %s\n", timeName);
@@ -195,5 +267,5 @@
     // with a call to psLogSetLevel().
     //
-    int logLevel = psMetadataLookupS32(&mdok, *site, "LOGLEVEL");
+    int logLevel = psMetadataLookupS32(&mdok, config->site, "LOGLEVEL");
     if (mdok && logLevel >= 0) {
         psTrace(__func__, 7, "Setting log level to %d\n", logLevel);
@@ -206,5 +278,5 @@
     // with a call to psLogSetFormat().
     //
-    psString logFormat = psMetadataLookupStr(&mdok, *site, "LOGFORMAT");
+    psString logFormat = psMetadataLookupStr(&mdok, config->site, "LOGFORMAT");
     if (mdok && logFormat) {
         psTrace(__func__, 7, "Setting log format to %s\n", logFormat);
@@ -218,16 +290,22 @@
     // XXX: This is not spec'ed in the SDRS.
     //
-    psString logDest = psMetadataLookupStr(&mdok, *site, "LOGDEST");
+    psString logDest = psMetadataLookupStr(&mdok, config->site, "LOGDEST");
     if (mdok && logDest) {
-        // XXX: Only stdout is provided for now; this section should be
+        // XXX: Only stdout and stderr are provided for now; this section should be
         // expanded in the future to do files, and perhaps even sockets.
-        if (strcasecmp(logDest, "STDOUT") != 0) {
-            psLogMsg(__func__, PS_LOG_WARN, "Only STDOUT is currently supported as a log destination.\n");
+        int logFD = STDIN_FILENO; // a known invalid value
+        if (!strcasecmp(logDest, "STDOUT")) {
+            logFD = STDOUT_FILENO;
+        }
+        if (!strcasecmp(logDest, "STDERR")) {
+            logFD = STDERR_FILENO;
+        }
+        if (logFD == STDIN_FILENO) {
+            psLogMsg(__func__, PS_LOG_WARN, "Only STDERR and STDOUT currently supported as a log destination.\n");
+            logFD = STDERR_FILENO;
         }
         psTrace(__func__, 7, "Setting log destination to STDOUT.\n");
-        // XXX: Use something other than "1"
-        psLogSetDestination(1);
-    }
-
+        psLogSetDestination(logFD);
+    }
 
     //
@@ -236,5 +314,5 @@
     // XXX: This is not spec'ed in the SDRS.
     //
-    psMetadata *trace = psMetadataLookupMD(&mdok, *site, "TRACE");
+    psMetadata *trace = psMetadataLookupMD(&mdok, config->site, "TRACE");
     if (mdok && trace) {
         psMetadataIterator *traceIter = psMetadataIteratorAlloc(trace, PS_LIST_HEAD, NULL); // Iterator
@@ -264,14 +342,16 @@
         psLogSetLevel(saveLogLevel);
     }
-    return(true);
-}
-
-bool pmConfigValidateCamera(
-    const psMetadata *camera,
+
+    return config;
+}
+
+
+bool pmConfigValidateCameraFormat(
+    const psMetadata *cameraFormat,
     const psMetadata *header)
 {
-    // Read the rule for that camera
+    // Read the rule for that camera format
     bool mdStatus = true;
-    psMetadata *rule = psMetadataLookupMD(&mdStatus, camera, "RULE");
+    psMetadata *rule = psMetadataLookupMD(&mdStatus, cameraFormat, "RULE");
     if (! mdStatus || ! rule) {
         psLogMsg(__func__, PS_LOG_WARN, "Unable to read rule for camera.\n");
@@ -282,10 +362,10 @@
     psMetadataIterator *ruleIter = psMetadataIteratorAlloc(rule, PS_LIST_HEAD, NULL); // Rule iterator
     psMetadataItem *ruleItem = NULL;    // Item from the metadata
-    bool match = true;                  // Does it match?
-    while ((ruleItem = psMetadataGetAndIncrement(ruleIter)) && match) {
+    while ((ruleItem = psMetadataGetAndIncrement(ruleIter))) {
         // Check for the existence of the rule
         psMetadataItem *headerItem = psMetadataLookup((psMetadata*)header, ruleItem->name);
         if (! headerItem || headerItem->type != ruleItem->type) {
-            match = false;
+            psFree(ruleIter);
+            return false;
         }
 
@@ -297,5 +377,6 @@
             if (strncmp(ruleItem->data.V, headerItem->data.V,
                         strlen(ruleItem->data.V)) != 0) {
-                match = false;
+                psFree(ruleIter);
+                return false;
             }
             break;
@@ -305,5 +386,6 @@
                     ruleItem->data.S32, headerItem->data.S32);
             if (ruleItem->data.S32 != headerItem->data.S32) {
-                match = false;
+                psFree(ruleIter);
+                return false;
             }
             break;
@@ -312,5 +394,6 @@
                     ruleItem->data.F32, headerItem->data.F32);
             if (ruleItem->data.F32 != headerItem->data.F32) {
-                match = false;
+                psFree(ruleIter);
+                return false;
             }
             break;
@@ -319,5 +402,6 @@
                     ruleItem->data.F64, headerItem->data.F64);
             if (ruleItem->data.F64 != headerItem->data.F64) {
-                match = false;
+                psFree(ruleIter);
+                return false;
             }
             break;
@@ -329,93 +413,196 @@
 
     psFree(ruleIter);
-
-    return match;
-}
-
-
-
-// Work out what camera we have, based on the FITS header and a set of
-// rules specified in the IPP configuration; return the camera configuration
-psMetadata *pmConfigCameraFromHeader(
-    const psMetadata *ipprc,            // The IPP configuration
-    const psMetadata *header)           // The FITS header
-{
-    bool mdStatus = false;  // Metadata lookup status
-    psMetadata *cameras = psMetadataLookupMD(&mdStatus, ipprc, "CAMERAS");
-    if (! mdStatus) {
-        psError(PS_ERR_IO, false, "Unable to find CAMERAS in the configuration.\n");
-        return NULL;
-    }
-    psMetadata *winner = NULL;       // The camera configuration whose rule first matches
-    //  the supplied header
-    // Iterate over the cameras
-    psMetadataIterator *iterator = psMetadataIteratorAlloc(cameras, PS_LIST_HEAD, NULL);
-    psMetadataItem *cameraItem = NULL; // Item from the metadata
-
-    while ((cameraItem = psMetadataGetAndIncrement(iterator))) {
-        // Open the camera information
-        psTrace(__func__, 3, "Inspecting camera %s (%s)\n", cameraItem->name,
-                cameraItem->comment);
-        psMetadata *camera = NULL; // The camera metadata
-        if (cameraItem->type == PS_DATA_METADATA) {
-            camera = psMemIncrRefCounter(cameraItem->data.md);
-        } else if (cameraItem->type == PS_DATA_STRING) {
-            psTrace(__func__, 5, "Reading camera configuration for %s...\n", cameraItem->name);
-            unsigned int badLines = 0;  // Number of bad lines in reading camera configuration
-            camera = psMetadataConfigParse(NULL, &badLines, cameraItem->data.V, true);
+    return true;
+}
+
+
+static bool formatFromHeader(psMetadata **format, // Format to return
+                             psMetadata *camera, // Camera configuration
+                             const psMetadata *header, // FITS header
+                             const char *cameraName // Name of camera
+                            )
+{
+    psMetadata *testFormat;
+
+    assert(format);
+    assert(camera);
+    assert(header);
+
+    bool result = false;                // Did we find the first match?
+
+    // Read the list of formats
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS"); // List of formats
+    if (!mdok || !formats) {
+        psLogMsg(__func__, PS_LOG_WARN, "Unable to find list of FORMATS in camera %s --- ignored\n",
+                 cameraName);
+        return false;
+    }
+    // Iterate over the formats
+    psMetadataIterator *formatsIter = psMetadataIteratorAlloc(formats, PS_LIST_HEAD, NULL);
+    psMetadataItem *formatsItem = NULL; // Item from formats
+    while ((formatsItem = psMetadataGetAndIncrement(formatsIter))) {
+        if (formatsItem->type != PS_DATA_STRING) {
+            psLogMsg(__func__, PS_LOG_WARN, "In camera %s, camera format %s is not of type STR --- "
+                     "ignored.\n", cameraName, formatsItem->name);
+            continue;
+        }
+        psTrace(__func__, 5, "Reading camera format for %s...\n", formatsItem->name);
+        // unsigned int badLines = 0;  // Number of bad lines in reading camera configuration
+        // Format to test against what we've got
+
+        if (!readConfig(&testFormat, formatsItem->data.V, formatsItem->name)) {
+            psLogMsg(__func__, PS_LOG_WARN, "trouble reading reading camera format %s\n", formatsItem->name);
+            psFree(testFormat);
+            continue;
+        }
+
+        # if (0)
+            psMetadata *testFormat = psMetadataConfigParse(NULL, &badLines, formatsItem->data.V, true);
+        if (badLines > 0) {
+            psLogMsg(__func__, PS_LOG_WARN, "%d bad lines encountered while reading camera"
+                     "format %s\n", badLines, formatsItem->name);
+        }
+        # endif
+
+        if (pmConfigValidateCameraFormat(testFormat, header)) {
+            if (!*format) {
+                psLogMsg(__func__, PS_LOG_INFO, "Camera %s, format %s matches header.\n", cameraName,
+                         formatsItem->name);
+                *format = psMemIncrRefCounter(testFormat);
+                result = true;
+            } else {
+                psLogMsg(__func__, PS_LOG_WARN, "Camera %s, format %s also matches header --- ignored.\n",
+                         cameraName, formatsItem->name);
+            }
+        }
+        psFree(testFormat);
+    }
+    psFree(formatsIter);
+
+    return result;
+}
+
+// Work out what camera we have, based on the FITS header and a set of rules specified in the IPP
+// configuration; return the camera configuration and format
+psMetadata *pmConfigCameraFormatFromHeader(
+    pmConfig *config,                   // The configuration
+    const psMetadata *header           // The FITS header
+)
+{
+    psMetadata *format = NULL;          // The winning format
+    bool mdok = false;                  // Metadata lookup status
+    psMetadata *testCamera = NULL;
+
+    // If we don't know what sort of camera we have, we try all that we know
+    if (! config->camera) {
+        psMetadata *cameras = psMetadataLookupMD(&mdok, config->site, "CAMERAS");
+        if (! mdok) {
+            psError(PS_ERR_IO, false, "Unable to find CAMERAS in the configuration.\n");
+            return false;
+        }
+
+        // Iterate over the cameras
+        psMetadataIterator *camerasIter = psMetadataIteratorAlloc(cameras, PS_LIST_HEAD, NULL);
+        psMetadataItem *camerasItem = NULL; // Item from the metadata
+        while ((camerasItem = psMetadataGetAndIncrement(camerasIter))) {
+            // Open the camera information
+            psTrace(__func__, 3, "Inspecting camera %s (%s)\n", camerasItem->name, camerasItem->comment);
+            if (camerasItem->type != PS_DATA_STRING) {
+                psLogMsg(__func__, PS_LOG_WARN, "Camera configuration for %s in CAMERAS is not of type STR "
+                         "--- ignored.\n", camerasItem->name);
+                continue;
+            }
+
+            psTrace(__func__, 5, "Reading camera configuration for %s...\n", camerasItem->name);
+            // unsigned int badLines = 0;  // Number of bad lines in reading camera configuration
+            // Camera to test against what we've got:
+
+            if (!readConfig(&testCamera, camerasItem->data.V, camerasItem->name)) {
+                psLogMsg(__func__, PS_LOG_WARN, "trouble reading reading camera configuration %s\n", camerasItem->name);
+                psFree(testCamera);
+                continue;
+            }
+
+            # if (0)
+                psMetadata *testCamera = psMetadataConfigParse(NULL, &badLines, camerasItem->data.V, true);
             if (badLines > 0) {
                 psLogMsg(__func__, PS_LOG_WARN, "%d bad lines encountered while reading camera"
-                         "configuration %s\n", badLines, cameraItem->name);
-            }
-        }
-
-        if (! camera) {
-            psLogMsg(__func__, PS_LOG_WARN, "Unable to interpret camera configuration for %s (%s)\n",
-                     cameraItem->name, cameraItem->comment);
-            continue;
-        }
-
-        if (pmConfigValidateCamera(camera, header)) {
-            if (! winner) {
-                // This is the first match
-                winner = psMemIncrRefCounter(camera);
-                psLogMsg(__func__, PS_LOG_INFO, "FITS header matches camera %s\n",
-                         cameraItem->name);
-            } else {
-                // We have a duplicate match
-                psLogMsg(__func__, PS_LOG_WARN, "Additional camera found that matches the rules: %s\n",
-                         cameraItem->name);
-            }
-        } // Done inspecting the camera
-
-        psFree(camera);
-
-    } // Done looking at all cameras
-    if (! winner) {
-        psError(PS_ERR_IO, true, "Unable to find an camera that matches input FITS header!\n");
-    }
-
-    psFree(iterator);
-    return winner;
-}
-
-psMetadata *pmConfigRecipeFromCamera(
-    const psMetadata *camera,
-    const char *recipeName)
-{
-    PS_ASSERT_PTR_NON_NULL(camera, false);
-    PS_ASSERT_PTR_NON_NULL(recipeName, false);
-
-    psMetadata *recipe = NULL;          // Recipe to read
+                         "configuration %s\n", badLines, camerasItem->name);
+            }
+            # endif
+
+            if (! testCamera) {
+                psLogMsg(__func__, PS_LOG_WARN, "Unable to interpret camera configuration for %s (%s) --- "
+                         "ignored\n", camerasItem->name, camerasItem->comment);
+                continue;
+            }
+
+            if (formatFromHeader(&format, testCamera, header, camerasItem->name)) {
+                config->camera = psMemIncrRefCounter(testCamera);
+            }
+            psFree(testCamera);
+        } // Done looking at all cameras
+        psFree(camerasIter);
+
+        if (! config->camera) {
+            psError(PS_ERR_IO, true, "Unable to find a camera that matches input FITS header!\n");
+            return NULL;
+        }
+
+        // Now we have the camera, we can read the recipes
+        if (!config->recipes) {
+            pmConfigReadRecipes(config);
+        }
+
+        return format;
+    }
+
+    // Otherwise, try the specific camera
+    if (! formatFromHeader(&format, config->camera, header, "specified camera")) {
+        psError(PS_ERR_IO, true, "Unable to find a format with the specified camera that matches the "
+                "given header.\n");
+        return NULL;
+    }
+    return format;
+}
+
+bool pmConfigReadRecipes(
+    pmConfig *config
+)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(config->camera, false);
+
+    if (!config->recipes) {
+        config->recipes = psMetadataAlloc();
+    }
+
     bool mdok = true;                   // Status of MD lookup
-    psMetadata *recipes = psMetadataLookupMD(&mdok, camera, "RECIPES"); // The list of recipes
+    psMetadata *recipes = psMetadataLookupMD(&mdok, config->camera, "RECIPES"); // The list of recipes
     if (! mdok || ! recipes) {
         psLogMsg(__func__, PS_LOG_WARN, "RECIPES in the camera configuration file is not of type METADATA\n");
-    } else {
-        psString recipeFileName = psMetadataLookupStr(&mdok, recipes, recipeName);
-        (void)readConfig(&recipe, recipeFileName, "recipe");
-    }
-
-    return recipe;
+        return false;
+    }
+    // Go through the recipes and load each one
+    psMetadataIterator *recipesIter = psMetadataIteratorAlloc(recipes, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *recipesItem = NULL; // Item from iteration
+    while ((recipesItem = psMetadataGetAndIncrement(recipesIter))) {
+        if (recipesItem->type != PS_DATA_STRING) {
+            psLogMsg(__func__, PS_LOG_WARN, "Filename for recipe %s isn't of type STR --- ignored.\n",
+                     recipesItem->name);
+            continue;
+        }
+
+        psMetadata *recipe = NULL;      // Recipe from file
+        if (readConfig(&recipe, recipesItem->data.V, "recipe")) {
+            psMetadataAdd(config->recipes, PS_LIST_TAIL, recipesItem->name,
+                          PS_DATA_METADATA | PS_META_REPLACE, recipesItem->comment, recipe);
+        }
+        psFree(recipe);                 // Drop reference
+    }
+    psFree(recipesIter);
+
+    return true;
 }
 
@@ -423,15 +610,9 @@
 pmConfigDB(*site)
  
+XXX: this should allow the option of having NO database server, if chosen by config
 XXX: What should we use for the Database namespace in the call to psDBInit()?
 This is currently NULL.
  *****************************************************************************/
 
-#ifdef OMIT_PSDB
-psDB *pmConfigDB(psMetadata *site)
-{
-    psError(PS_ERR_UNKNOWN, true, "pslib was built without psDB support");
-    return NULL;
-}
-#else
 psDB *pmConfigDB(
     psMetadata *site)
@@ -442,27 +623,111 @@
     psBool mdStatus03 = false;
 
+    // XXX leaky strings
     psString dbServer = psMetadataLookupStr(&mdStatus01, site, "DBSERVER");
     psString dbUsername = psMetadataLookupStr(&mdStatus02, site, "DBUSER");
     psString dbPassword = psMetadataLookupStr(&mdStatus03, site, "DBPASSWORD");
     psString dbName = psMetadataLookupStr(&mdStatus01, site, "DBNAME");
-
     if (!(mdStatus01 & mdStatus02 & mdStatus03)) {
         psLogMsg(__func__, PS_LOG_WARN, "Could not determine database server name, userID, and password from site metadata.\n");
-        return NULL;
-    }
-
-
-
-    psDB *dbh = psDBInit(dbServer, dbUsername, dbPassword, dbName);
-    psFree(dbServer);
-    psFree(dbUsername);
-    psFree(dbPassword);
-    psFree(dbName);
-
-    if (!dbh) {
-        psError(PS_ERR_UNKNOWN, false, "database connection failed");
-    }
-
-    return dbh;
-}
-#endif
+        return(NULL);
+    }
+
+    return(psDBInit(dbServer, dbUsername, dbPassword, dbName));
+}
+
+
+bool pmConfigConformHeader(psMetadata *header, // Header to conform
+                           const psMetadata *format // Camera format
+                          )
+{
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *rules = psMetadataLookupMD(&mdok, format, "RULE"); // How to identify this format
+    if (!mdok || !rules) {
+        psError(PS_ERR_IO, false, "Unable to find RULE in camer format.\n");
+        return false;
+    }
+
+    psMetadataIterator *rulesIter = psMetadataIteratorAlloc(rules, PS_LIST_HEAD, NULL); // Iterator for rules
+    psMetadataItem *rulesItem = NULL;   // Item from iteration
+    while ((rulesItem = psMetadataGetAndIncrement(rulesIter))) {
+        psMetadataItem *newItem = psMetadataItemCopy(rulesItem); // Copy of item
+        psMetadataAddItem(header, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+        psFree(newItem);                // Drop reference
+    }
+    psFree(rulesIter);
+
+    return true;
+}
+
+// given the 'file' and 'list' words, find the arguments associated with these words
+// and interpret them as lists of files.  return an array of the resulting filenames.
+psArray *pmConfigFileSets (int *argc, char **argv, char *file, char *list)
+{
+
+    int Narg;
+
+    // we load all input files onto a psArray, to be parsed later
+    psArray *input = psArrayAlloc (16);
+    input->n = 0;
+
+    // load the list of filenames the supplied file (may be a glob: "file*.fits")
+    if ((Narg = psArgumentGet (*argc, argv, file))) {
+        glob_t globList;
+        psArgumentRemove (Narg, argc, argv);
+        globList.gl_offs = 0;
+        glob (argv[Narg], 0, NULL, &globList);
+        for (int i = 0; i < globList.gl_pathc; i++) {
+            char *filename = psStringCopy (globList.gl_pathv[i]);
+            psArrayAdd (input, 16, filename);
+            psFree (filename);
+        }
+        psArgumentRemove (Narg, argc, argv);
+    }
+
+    // load the list from the supplied text file
+    if ((Narg = psArgumentGet (*argc, argv, list))) {
+        int nItems;
+        char line[1024]; // XXX limits the list lines to 1024 chars
+        char word[1024];
+        char *filename;
+
+        psArgumentRemove (Narg, argc, argv);
+        FILE *f = fopen (argv[Narg], "r");
+        if (f == NULL) {
+            psAbort ("psphot", "unable to open specified list file");
+        }
+        while (fgets (line, 1024, f) != NULL) {
+            nItems = sscanf (line, "%s", word);
+            switch (nItems) {
+            case 0:
+                break;
+            case 1:
+                filename = psStringCopy (word);
+                psArrayAdd (input, 16, filename);
+                psFree (filename);
+                break;
+            default:
+                // rigid format, no comments allowed?
+                psAbort ("pmConfig", "error parsing input list file");
+                break;
+            }
+        }
+        psArgumentRemove (Narg, argc, argv);
+    }
+
+    return input;
+}
+
+bool pmConfigFileSetsMD (psMetadata *metadata, int *argc, char **argv, char *name, char *file, char *list)
+{
+
+    psArray *files = pmConfigFileSets (argc, argv, file, list);
+    if (files->n == 0) {
+        psFree (files);
+        return false;
+    }
+
+    psMetadataAddPtr (metadata, PS_LIST_TAIL, name,  PS_DATA_ARRAY, "", files);
+    psFree (files);
+    return true;
+}
