/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
   Copyright (c) 2013 D. Richard Hipp
  
   This program is free software; you can redistribute it and/or
   modify it under the terms of the Simplified BSD License (also
   known as the "2-Clause License" or "FreeBSD 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.
  
   Author contact information:
     drh@hwaci.com
     http://www.hwaci.com/drh/
  
  *****************************************************************************
   This file implements (most of) the fsl_xxx APIs related to handling
   configuration data from the db(s).
*/
#include "fossil-scm/fossil-internal.h"

#include <assert.h>
#include <stdlib.h> /* bsearch() */

/**
   File-local macro used to ensure that all cached statements used by
   the fsl_config_get_xxx() APIs use an equivalent string (so that
   they use the same underlying cache fsl_stmt handle).  The %s
   represents one of the config table names (config, vvfar,
   global_config). Normally we wouldn't use %s in a cached statement,
   but we're only expecting 3 values for table here and each one will
   only be cached once.
*/
#define SELECT_FROM_CONFIG "SELECT value FROM %s WHERE name=?"

char const * fsl_config_table_for_role(fsl_confdb_t mode){
  switch(mode){
    case FSL_CONFDB_REPO: return "config";
    case FSL_CONFDB_CKOUT: return "vvar";
    case FSL_CONFDB_GLOBAL: return "global_config";
    default:
      assert(!"Invalid fsl_confdb_t value");
      return NULL;
  }
}

fsl_db * fsl_config_for_role(fsl_cx * f, fsl_confdb_t mode){
  switch(mode){
    case FSL_CONFDB_REPO: return fsl_cx_db_repo(f);
    case FSL_CONFDB_CKOUT: return fsl_cx_db_checkout(f);
    case FSL_CONFDB_GLOBAL: return fsl_cx_db_config(f);
    default:
      assert(!"Invalid fsl_confdb_t value");
      return NULL;
  }
}



int fsl_config_unset( fsl_cx * f, fsl_confdb_t mode, char const * key ){
  fsl_db * db = fsl_config_for_role(f, mode);
  if(!db || !key || !*key) return FSL_RC_MISUSE;
  else{
    char const * table = fsl_config_table_for_role(mode);
    assert(table);
    return fsl_db_exec(db, "DELETE FROM %s WHERE name=%Q", table, key);
  }
}

fsl_int32_t fsl_config_get_int32( fsl_cx * f, fsl_confdb_t mode,
                                  fsl_int32_t dflt, char const * key ){
  fsl_db * db = fsl_config_for_role(f, mode);
  char const * table = fsl_config_table_for_role(mode);
  fsl_int32_t rv = dflt;
  assert(table);
  if(db){
    fsl_stmt * st = NULL;
    fsl_db_prepare_cached(db, &st,
                          SELECT_FROM_CONFIG, table);
    if(st){
      fsl_stmt_bind_text(st, 1, key, -1, 0);
      if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
        rv = fsl_stmt_g_int32(st, 0);
      }
      fsl_stmt_cached_yield(st);
    }
  }
  return rv;
}

fsl_int32_t fsl_get_int32_local( fsl_cx * f, fsl_int32_t dflt,
                               char const * key ){
  return fsl_config_get_int32( f, FSL_CONFDB_CKOUT, dflt, key );
}

fsl_int32_t fsl_get_int32_repo( fsl_cx * f, fsl_int32_t dflt,
                               char const * key ){
  return fsl_config_get_int32( f, FSL_CONFDB_REPO, dflt, key );
}

fsl_int32_t fsl_get_int32_global( fsl_cx * f, fsl_int32_t dflt,
                                char const * key ){
  return fsl_config_get_int32( f, FSL_CONFDB_GLOBAL, dflt, key );
}

fsl_int64_t fsl_config_get_int64( fsl_cx * f, fsl_confdb_t mode,
                                  fsl_int64_t dflt, char const * key ){
  fsl_int64_t rv = dflt;
  fsl_db * db = fsl_config_for_role(f, mode);
  char const * table = fsl_config_table_for_role(mode);
  assert(table);
  if(db){
    fsl_stmt * st = NULL;
    fsl_db_prepare_cached(db, &st,
                          SELECT_FROM_CONFIG, table);
    if(st){
      fsl_stmt_bind_text(st, 1, key, -1, 0);
      if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
        rv = fsl_stmt_g_int64(st, 0);
      }
      fsl_stmt_cached_yield(st);
    }
  }
  return rv;
}


fsl_int64_t fsl_get_int64_local( fsl_cx * f, fsl_int64_t dflt,
                               char const * key ){
  return fsl_config_get_int64( f, FSL_CONFDB_CKOUT, dflt, key );
}

fsl_int64_t fsl_get_int64_repo( fsl_cx * f, fsl_int64_t dflt,
                              char const * key ){
  return fsl_config_get_int64( f, FSL_CONFDB_REPO, dflt, key );
}

fsl_int64_t fsl_get_int64_global( fsl_cx * f, fsl_int64_t dflt,
                                char const * key ){
  return fsl_config_get_int64( f, FSL_CONFDB_GLOBAL, dflt, key );
}

fsl_id_t fsl_config_get_id( fsl_cx * f, fsl_confdb_t mode,
                             fsl_id_t dflt, char const * key ){
  fsl_db * db = fsl_config_for_role(f, mode);
  char const * table = fsl_config_table_for_role(mode);
  fsl_id_t rv = dflt;
  assert(table);
  if(db){
    fsl_stmt * st = NULL;
    fsl_db_prepare_cached(db, &st,
                          SELECT_FROM_CONFIG, table);
    if(st){
      fsl_stmt_bind_text(st, 1, key, -1, 0);
      if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
        rv = fsl_stmt_g_id(st, 0);
      }
      fsl_stmt_cached_yield(st);
    }
  }
  return rv;
}


fsl_id_t fsl_get_id_local( fsl_cx * f, fsl_id_t dflt,
                         char const * key ){
  return fsl_config_get_id( f, FSL_CONFDB_CKOUT, dflt, key );
}

fsl_id_t fsl_get_id_repo( fsl_cx * f, fsl_id_t dflt,
                        char const * key ){
  return fsl_config_get_id( f, FSL_CONFDB_REPO, dflt, key );
}

fsl_id_t fsl_get_id_global( fsl_cx * f, fsl_id_t dflt,
                        char const * key ){
  return fsl_config_get_id( f, FSL_CONFDB_GLOBAL, dflt, key );
}


fsl_double_t fsl_config_get_double( fsl_cx * f, fsl_confdb_t mode,
                                     fsl_double_t dflt, char const * key ){
  fsl_db * db = fsl_config_for_role(f, mode);
  char const * table = fsl_config_table_for_role(mode);
  fsl_double_t rv = dflt;
  assert(table);
  if(db){
    fsl_stmt * st = NULL;
    fsl_db_prepare_cached(db, &st,
                          SELECT_FROM_CONFIG, table);
    if(st){
      fsl_stmt_bind_text(st, 1, key, -1, 0);
      if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
        rv = fsl_stmt_g_double(st, 0);
      }
      fsl_stmt_cached_yield(st);
    }
  }
  return rv;
}

fsl_double_t fsl_get_double_local( fsl_cx * f, fsl_double_t dflt,
                                 char const * key ){
  return fsl_config_get_double( f, FSL_CONFDB_CKOUT, dflt, key );
}

fsl_double_t fsl_get_double_repo( fsl_cx * f, fsl_double_t dflt,
                                char const * key ){
  return fsl_config_get_double( f, FSL_CONFDB_REPO, dflt, key );
}

fsl_double_t fsl_get_double_global( fsl_cx * f, fsl_double_t dflt,
                                char const * key ){
  return fsl_config_get_double( f, FSL_CONFDB_GLOBAL, dflt, key );
}



char * fsl_config_get_text( fsl_cx * f, fsl_confdb_t mode,
                             char const * key, fsl_size_t * len ){
  fsl_db * db = fsl_config_for_role(f, mode);
  char const * table = fsl_config_table_for_role(mode);
  char * rv = NULL;
  assert(table);
  if(db){
    fsl_stmt * st = NULL;
    fsl_db_prepare_cached(db, &st,
                          SELECT_FROM_CONFIG, table);
    if(st){
      fsl_stmt_bind_text(st, 1, key, -1, 0);
      if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
        char const * s = fsl_stmt_g_text(st, 0, len);
        rv = s ? fsl_strndup( s, len ? (fsl_int_t)*len : -1) : NULL;
      }
      fsl_stmt_cached_yield(st);
    }
  }
  return rv;
}

char * fsl_config_get_text2( fsl_cx * f, fsl_confdb_t mode,
                             char const * key, fsl_size_t * len ){
  char * rv = NULL;
  fsl_config_ctrl const * fcc = (f && f->ckout.dir)
    ? fsl_config_ctrl_get(key)
    : NULL;
  if(fcc && fcc->versionable){
    fsl_config_get_versionable( f, key, &rv );
    if(rv){
      if(len) *len = fsl_strlen(rv);
      return rv;
    }
  }
  return fsl_config_get_text(f, mode, key, len);
}


int fsl_config_get_buffer( fsl_cx * f, fsl_confdb_t mode,
                            char const * key, fsl_buffer * b ){
  fsl_db * db = fsl_config_for_role(f, mode);
  char const * table = fsl_config_table_for_role(mode);
  int rc = FSL_RC_NOT_FOUND;
  assert(table);
  if(db){
    fsl_stmt * st = NULL;
    rc = fsl_db_prepare_cached(db, &st,
                               SELECT_FROM_CONFIG, table);
    if(!rc){
      fsl_stmt_bind_text(st, 1, key, -1, 0);
      if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
        fsl_size_t len = 0;
        char const * s = fsl_stmt_g_text(st, 0, &len);
        rc = s ? fsl_buffer_append(b, s, len) : 0;
      }else{
        rc = FSL_RC_NOT_FOUND;
      }
      fsl_stmt_cached_yield(st);
    }
  }
  return rc;
}


int fsl_config_get_buffer2( fsl_cx * f, fsl_confdb_t mode,
                            char const * key,
                            fsl_buffer * b ){
  char * rv = NULL;
  fsl_config_ctrl const * fcc = (f && f->ckout.dir)
    ? fsl_config_ctrl_get(key)
    : NULL;
  if(fcc && fcc->versionable){
    fsl_config_get_versionable( f, key, &rv );
    if(rv){
      int const rc = fsl_buffer_append(b, rv, -1);
      fsl_free(rv);
      return rc;
    }
  }
  return fsl_config_get_buffer(f, mode, key, b);
}


char fsl_config_get_bool( fsl_cx * f,
                          fsl_confdb_t mode,
                          char dflt,
                          char const * key ){
  int rc;
  char rv = dflt;
  fsl_stmt * st = NULL;
  char const * table = fsl_config_table_for_role(mode);
  fsl_db * db;
  if(!f || !key || !*key) return dflt;
  db = fsl_config_for_role(f, mode);
  if(!db) return dflt;
  assert(table);
  rc = fsl_db_prepare_cached(db, &st,
                             SELECT_FROM_CONFIG,
                             table);
  if(!rc){
    fsl_stmt_bind_text(st, 1, key, -1, 0);
    if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
      char const * col = fsl_stmt_g_text(st, 0, NULL);
      rv = col ? fsl_str_bool(col) : dflt /* 0? */;
    }
    fsl_stmt_cached_yield(st);
  }
  return rv;
}

/**
    Sets up a REPLACE statement for the given config db and key. On
    success 0 is returned and *st holds the cached statement. The caller
    must bind() parameter #2 and step() the statement, then
    fsl_stmt_cached_yield() it.
   
    Returns non-0 on error.
*/
static int fsl_config_set_prepare( fsl_cx * f, fsl_stmt **st,
                                   fsl_confdb_t mode, char const * key ){
  char const * table = fsl_config_table_for_role(mode);
  fsl_db * db = fsl_config_for_role(f,mode);
  assert(table);
  if(!db || !key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  else{
    int rc = fsl_db_prepare_cached(db, st,
                                   "REPLACE INTO %s(name,value) VALUES(?,?)",
                                   table);
    if(!rc) rc = fsl_stmt_bind_text(*st, 1, key, -1, 1);
    if(rc && !f->error.code){
      fsl_cx_uplift_db_error(f, db);
    }
    return rc;
  }

}


/*
  TODO/FIXME: the fsl_config_set_xxx() routines all use the same basic
  structure, differing only in the concrete bind() op they call. They
  should be consolidated somehow.
*/


int fsl_config_set_text( fsl_cx * f, fsl_confdb_t mode,
                         char const * key, char const * val ){
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db || !key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  else{
    fsl_stmt * st = NULL;
    int rc = fsl_config_set_prepare(f, &st, mode, key);
    if(!rc){
      if(val){
        rc = fsl_stmt_bind_text(st, 2, val, -1, 0);
      }else{
        rc = fsl_stmt_bind_null(st, 2);
      }
      if(!rc){
        rc = fsl_stmt_step(st);
      }
      fsl_stmt_cached_yield(st);
      if(FSL_RC_STEP_DONE==rc) rc = 0;
    }
    if(rc && !f->error.code) fsl_cx_uplift_db_error(f, db);
    return rc;
  }
}

int fsl_config_set_blob( fsl_cx * f, fsl_confdb_t mode, char const * key,
                         void const * val, fsl_int_t len ){
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db || !key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  else{
    fsl_stmt * st = NULL;
    int rc = fsl_config_set_prepare(f, &st, mode, key);
    if(!rc){
      if(val){
        if(len<0) len = fsl_strlen((char const *)val);
        rc = fsl_stmt_bind_blob(st, 2, val, len, 0);
      }else{
        rc = fsl_stmt_bind_null(st, 2);
      }
      if(!rc){
        rc = fsl_stmt_step(st);
      }
      fsl_stmt_cached_yield(st);
      if(FSL_RC_STEP_DONE==rc) rc = 0;
    }
    if(rc && !f->error.code) fsl_cx_uplift_db_error(f, db);
    return rc;
  }
}
  
int fsl_config_set_int32( fsl_cx * f, fsl_confdb_t mode,
                          char const * key, fsl_int32_t val ){
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db || !key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  else{
    fsl_stmt * st = NULL;
    int rc = fsl_config_set_prepare(f, &st, mode, key);
    if(!rc){
      rc = fsl_stmt_bind_int32(st, 2, val);
      if(!rc){
        rc = fsl_stmt_step(st);
      }
      fsl_stmt_cached_yield(st);
      if(FSL_RC_STEP_DONE==rc) rc = 0;
    }
    if(rc && !f->error.code) fsl_cx_uplift_db_error(f, db);
    return rc;
  }
}

int fsl_config_set_int64( fsl_cx * f, fsl_confdb_t mode,
                          char const * key, fsl_int64_t val ){
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db || !key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  else{
    fsl_stmt * st = NULL;
    int rc = fsl_config_set_prepare(f, &st, mode, key);
    if(!rc){
      rc = fsl_stmt_bind_int64(st, 2, val);
      if(!rc){
        rc = fsl_stmt_step(st);
      }
      fsl_stmt_cached_yield(st);
      if(FSL_RC_STEP_DONE==rc) rc = 0;
    }
    if(rc && !f->error.code) fsl_cx_uplift_db_error(f, db);
    return rc;
  }
}

int fsl_config_set_id( fsl_cx * f, fsl_confdb_t mode,
                          char const * key, fsl_id_t val ){
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db || !key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  else{
    fsl_stmt * st = NULL;
    int rc = fsl_config_set_prepare(f, &st, mode, key);
    if(!rc){
      rc = fsl_stmt_bind_id(st, 2, val);
      if(!rc){
        rc = fsl_stmt_step(st);
      }
      fsl_stmt_cached_yield(st);
      if(FSL_RC_STEP_DONE==rc) rc = 0;
    }
    if(rc && !f->error.code) fsl_cx_uplift_db_error(f, db);
    return rc;
  }
}

int fsl_config_set_double( fsl_cx * f, fsl_confdb_t mode,
                           char const * key, fsl_double_t val ){
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db || !key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  else{
    fsl_stmt * st = NULL;
    int rc = fsl_config_set_prepare(f, &st, mode, key);
    if(!rc){
      rc = fsl_stmt_bind_double(st, 2, val);
      if(!rc){
        rc = fsl_stmt_step(st);
      }
      fsl_stmt_cached_yield(st);
      if(FSL_RC_STEP_DONE==rc) rc = 0;
    }
    if(rc && !f->error.code) fsl_cx_uplift_db_error(f, db);
    return rc;
  }
}

int fsl_config_set_bool( fsl_cx * f, fsl_confdb_t mode,
                         char const * key, char val ){
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db || !key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  else{
    fsl_stmt * st = NULL;
    int rc = fsl_config_set_prepare(f, &st, mode, key);
    if(!rc){
      rc = fsl_stmt_bind_int32(st, 2, val ? 1 : 0);
      if(!rc){
        rc = fsl_stmt_step(st);
      }
      fsl_stmt_cached_yield(st);
      if(FSL_RC_STEP_DONE==rc) rc = 0;
    }
    if(rc && !f->error.code) fsl_cx_uplift_db_error(f, db);
    return rc;
  }
}

int fsl_config_transaction_begin(fsl_cx * f, fsl_confdb_t mode){
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db) return FSL_RC_MISUSE;
  else{
    int rc = fsl_db_transaction_begin(db);
    if(rc) fsl_cx_uplift_db_error(f, db);
    return rc;
  }
}

int fsl_config_transaction_end(fsl_cx * f, fsl_confdb_t mode, char rollback){
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db) return FSL_RC_MISUSE;
  else{
    int rc = fsl_db_transaction_end(db, rollback);
    if(rc) fsl_cx_uplift_db_error(f, db);
    return rc;
  }
}
/*
  TODO: the infrastructure from v1's configure.c and db.c which deals
  with the config db and the list of known/allowed settings.

  ==================

static const struct {
  const char *zName;   / * Name of the configuration set * /
  int groupMask;       / * Mask for that configuration set * /
  const char *zHelp;   / * What it does * /

} fslConfigGroups[] = {
  { "/email",        FSL_CONFIGSET_ADDR,  "Concealed email addresses in tickets" },
  { "/project",      FSL_CONFIGSET_PROJ,  "Project name and description"         },
  { "/skin",         FSL_CONFIGSET_SKIN | FSL_CONFIGSET_CSS,
                                      "Web interface appearance settings"    },
  { "/css",          FSL_CONFIGSET_CSS,   "Style sheet"                          },
  { "/shun",         FSL_CONFIGSET_SHUN,  "List of shunned artifacts"            },
  { "/ticket",       FSL_CONFIGSET_TKT,   "Ticket setup",                        },
  { "/user",         FSL_CONFIGSET_USER,  "Users and privilege settings"         },
  { "/xfer",         FSL_CONFIGSET_XFER,  "Transfer setup",                      },
  { "/all",          FSL_CONFIGSET_ALL,   "All of the above"                     },
  {NULL, 0, NULL}
};
 */

/*
   The following is a list of settings that we are willing to
   transfer.
  
   Setting names that begin with an alphabetic characters refer to
   single entries in the CONFIG table.  Setting names that begin with
   "@" are for special processing.
*/
static struct {
  const char *zName;   /* Name of the configuration parameter */
  int groupMask;       /* Which config groups is it part of */
} fslConfigXfer[] = {
  { "css",                    FSL_CONFIGSET_CSS  },

  { "header",                 FSL_CONFIGSET_SKIN },
  { "footer",                 FSL_CONFIGSET_SKIN },
  { "logo-mimetype",          FSL_CONFIGSET_SKIN },
  { "logo-image",             FSL_CONFIGSET_SKIN },
  { "background-mimetype",    FSL_CONFIGSET_SKIN },
  { "background-image",       FSL_CONFIGSET_SKIN },
  { "index-page",             FSL_CONFIGSET_SKIN },
  { "timeline-block-markup",  FSL_CONFIGSET_SKIN },
  { "timeline-max-comment",   FSL_CONFIGSET_SKIN },
  { "timeline-plaintext",     FSL_CONFIGSET_SKIN },
  { "adunit",                 FSL_CONFIGSET_SKIN },
  { "adunit-omit-if-admin",   FSL_CONFIGSET_SKIN },
  { "adunit-omit-if-user",    FSL_CONFIGSET_SKIN },

  { "th1-setup",              FSL_CONFIGSET_ALL },

  { "tcl",                    FSL_CONFIGSET_SKIN|FSL_CONFIGSET_TKT|FSL_CONFIGSET_XFER },
  { "tcl-setup",              FSL_CONFIGSET_SKIN|FSL_CONFIGSET_TKT|FSL_CONFIGSET_XFER },

  { "project-name",           FSL_CONFIGSET_PROJ },
  { "project-description",    FSL_CONFIGSET_PROJ },
  { "manifest",               FSL_CONFIGSET_PROJ },
  { "binary-glob",            FSL_CONFIGSET_PROJ },
  { "clean-glob",             FSL_CONFIGSET_PROJ },
  { "ignore-glob",            FSL_CONFIGSET_PROJ },
  { "keep-glob",              FSL_CONFIGSET_PROJ },
  { "crnl-glob",              FSL_CONFIGSET_PROJ },
  { "encoding-glob",          FSL_CONFIGSET_PROJ },
  { "empty-dirs",             FSL_CONFIGSET_PROJ },
  { "allow-symlinks",         FSL_CONFIGSET_PROJ },

  { "ticket-table",           FSL_CONFIGSET_TKT  },
  { "ticket-common",          FSL_CONFIGSET_TKT  },
  { "ticket-change",          FSL_CONFIGSET_TKT  },
  { "ticket-newpage",         FSL_CONFIGSET_TKT  },
  { "ticket-viewpage",        FSL_CONFIGSET_TKT  },
  { "ticket-editpage",        FSL_CONFIGSET_TKT  },
  { "ticket-reportlist",      FSL_CONFIGSET_TKT  },
  { "ticket-report-template", FSL_CONFIGSET_TKT  },
  { "ticket-key-template",    FSL_CONFIGSET_TKT  },
  { "ticket-title-expr",      FSL_CONFIGSET_TKT  },
  { "ticket-closed-expr",     FSL_CONFIGSET_TKT  },
  { "@reportfmt",             FSL_CONFIGSET_TKT  },

  { "@user",                  FSL_CONFIGSET_USER },

  { "@concealed",             FSL_CONFIGSET_ADDR },

  { "@shun",                  FSL_CONFIGSET_SHUN },

  { "xfer-common-script",     FSL_CONFIGSET_XFER },
  { "xfer-push-script",       FSL_CONFIGSET_XFER },
};

/*
   Return a pointer to a string that contains the RHS of an IN
   operator that will select CONFIG table names that are part of the
   configuration that matches iMatch. The returned string must
   eventually be fsl_free()'d.
*/
char *fsl_config_inop_rhs(int iMask){
  fsl_buffer x = fsl_buffer_empty;
  const char *zSep = "";
  const int n = sizeof(fslConfigXfer)/sizeof(fslConfigXfer[0]);
  int i;
  int rc = fsl_buffer_append(&x, "(", 1);
  for(i=0; !rc && (i<n); i++){
    if( (fslConfigXfer[i].groupMask & iMask)==0 ) continue;
    if( fslConfigXfer[i].zName[0]=='@' ) continue;
    rc = fsl_buffer_appendf(&x, "%s'%s'", zSep, fslConfigXfer[i].zName);
    zSep = ",";
  }
  if(!rc){
    rc = fsl_buffer_append(&x, ")", 1);
  }
  if(rc){
    fsl_buffer_clear(&x);
    assert(!x.mem);
  }else{
    fsl_buffer_resize(&x, x.used);
  }
  return (char *)x.mem;
}



static fsl_config_ctrl const fslConfigCtrl[] = {
/*
  These MUST stay sorted by name and the .def field MUST have a
  non-NULL value so that some API guarantees can be made.
*/
  { "access-log",    0,                0, 0, "off"                 },
  { "allow-symlinks",0,                0, 1, "off"                 },
  { "auto-captcha",  "autocaptcha",    0, 0, "on"                  },
  { "auto-hyperlink",0,                0, 0, "on"                  },
  { "auto-shun",     0,                0, 0, "on"                  },
  { "autosync",      0,                0, 0, "on"                  },
  { "binary-glob",   0,               40, 1, ""                    },
  { "clearsign",     0,                0, 0, "off"                 },
#if defined(_WIN32) || defined(__CYGWIN__) || defined(__DARWIN__) || defined(__APPLE__)
  { "case-sensitive",0,                0, 0, "off"                 },
#else
  { "case-sensitive",0,                0, 0, "on"                  },
#endif
  { "clean-glob",    0,               40, 1, ""                    },
  { "crnl-glob",     0,               40, 1, ""                    },
  { "default-perms", 0,               16, 0, "u"                   },
  { "diff-binary",   0,                0, 0, "on"                  },
  { "diff-command",  0,               40, 0, ""                    },
  { "dont-push",     0,                0, 0, "off"                 },
  { "editor",        0,               32, 0, ""                    },
  { "empty-dirs",    0,               40, 1, ""                    },
  { "encoding-glob",  0,              40, 1, ""                    },
  { "gdiff-command", 0,               40, 0, "gdiff"               },
  { "gmerge-command",0,               40, 0, ""                    },
  { "http-port",     0,               16, 0, "8080"                },
  { "https-login",   0,                0, 0, "off"                 },
  { "ignore-glob",   0,               40, 1, ""                    },
  { "keep-glob",     0,               40, 1, ""                    },
  { "localauth",     0,                0, 0, "off"                 },
  { "main-branch",   0,               40, 0, "trunk"               },
  { "manifest",      0,                0, 1, "off"                 },
  { "max-upload",    0,               25, 0, "250000"              },
  { "mtime-changes", 0,                0, 0, "on"                  },
  { "pgp-command",   0,               40, 0, "gpg --clearsign -o " },
  { "proxy",         0,               32, 0, "off"                 },
  { "relative-paths",0,                0, 0, "on"                  },
  { "repo-cksum",    0,                0, 0, "on"                  },
  { "self-register", 0,                0, 0, "off"                 },
  { "ssh-command",   0,               40, 0, ""                    },
  { "ssl-ca-location",0,              40, 0, ""                    },
  { "ssl-identity",  0,               40, 0, ""                    },
  { "tcl",           0,                0, 0, "off"                 },
  { "tcl-setup",     0,               40, 0, ""                    },
  { "th1-setup",     0,               40, 0, ""                    },
  { "web-browser",   0,               32, 0, ""                    },
  { "white-foreground", 0,             0, 0, "off"                 },
  { 0,0,0,0,0 }
};

char *fsl_db_setting_inop_rhs(){
  fsl_buffer x = fsl_buffer_empty;
  const char *zSep = "";
  fsl_config_ctrl const * ct = &fslConfigCtrl[0];
  int rc = fsl_buffer_append(&x, "(", 1);
  for( ; !rc && ct && ct->name; ++ct){
    rc = fsl_buffer_appendf(&x, "%s'%s'", zSep, ct->name);
    zSep = ",";
  }
  if(!rc){
    rc = fsl_buffer_append(&x, ")", 1);
  }
  if(rc){
    fsl_buffer_clear(&x);
    assert(!x.mem);
  }else{
    fsl_buffer_resize(&x, x.used);
  }
  return (char *)x.mem;
}

static int fsl_config_ctrl_cmp(const void *lhs, const void *rhs){
  fsl_config_ctrl const * l = (fsl_config_ctrl const *)lhs;
  fsl_config_ctrl const * r = (fsl_config_ctrl const *)rhs;
  if(!l) return r ? -1 : 0;
  else if(!r) return 1;
  else return fsl_strcmp(l->name, r->name);
}

fsl_config_ctrl const * fsl_config_ctrl_get(char const * key){
  fsl_config_ctrl const * fcc;
  fsl_config_ctrl bogo = {0,0,0,0,0};
  bogo.name = key;
  fcc = (fsl_config_ctrl const *)
    bsearch( &bogo, fslConfigCtrl,
             sizeof(fslConfigCtrl)/sizeof(fslConfigCtrl[0])
             -1/* for empty tail entry */,
             sizeof(fsl_config_ctrl),
             fsl_config_ctrl_cmp );
  return (fcc && fcc->name) ? fcc : NULL;
}

char fsl_config_key_is_valid(char const * key){
  fsl_config_ctrl const * fcc = fsl_config_ctrl_get(key);
  return (fcc && fcc->name) ? 1 : 0;
}

char fsl_config_key_is_versionable(char const * key){
  fsl_config_ctrl const * fcc = fsl_config_ctrl_get(key);
  return (fcc && fcc->versionable) ? 1 : 0;
}

char const * fsl_config_key_default_value(char const * key){
  fsl_config_ctrl const * fcc = fsl_config_ctrl_get(key);
  return (fcc && fcc->name) ? fcc->def : NULL;
}

/**
   Internal code reduction (replaced by even more docs about it).
   The two %s params must be f->ckout.dir and the config key.
*/
#define VERSIONABLE_FILENAME_PFMT "%s.fossil-settings/%s"
char fsl_config_has_versionable( fsl_cx * f, char const * key ){
  if(!f || !key || !*key || !f->ckout.dir) return 0;
  else if(!fsl_config_key_is_valid(key)) return 0;
  else{
    fsl_buffer * fn = &f->fsScratch;
    int rc;
    assert((0==fn->used) && "Misuse of f->fsScratch");
    rc = fsl_buffer_appendf(fn, VERSIONABLE_FILENAME_PFMT,
                            f->ckout.dir, key);
    if(!rc) rc = fsl_stat(fsl_buffer_cstr(fn), NULL, 1);
    fn->used = 0;
    return 0==rc;
  }
}

int fsl_config_get_versionable( fsl_cx * f, char const * key, char **pOut ){
  if(!f || !key || !*key || !pOut) return FSL_RC_MISUSE;
  else if(!f->ckout.dir) return FSL_RC_NOT_A_CHECKOUT;
  else{
    fsl_buffer content = fsl_buffer_empty
      /* Reminder to self: don't use f->fileContent here because that
         one is generally used to read arbitrarily large files and may
         have much more memory than we really need (and will transfer
         ownership of to the caller).
      */;
    char * rv = NULL;
    int rc;
    fsl_buffer * fname = f ? &f->fsScratch : NULL;
    assert((0==fname->used) && "Misuse of f->fsScratch buffer.");
    rc = fsl_buffer_appendf(fname, VERSIONABLE_FILENAME_PFMT,
                            f->ckout.dir, key);
    if(!rc){
      rc = fsl_buffer_fill_from_filename(&content,
                                         fsl_buffer_cstr(fname));
      if(!rc){
        if(content.used){
          if(content.capacity > (content.used*3/2)){
            fsl_buffer_resize(&content, content.used);
          }
          rv = (char *)content.mem;
          content = fsl_buffer_empty /* transfer .mem ownership */;
        }
      }
      fsl_buffer_clear(&content);
    }
    fname->used = 0;
    if(!rc) *pOut = rv;
    else{
      assert(!rv);
    }
    return rc;
  }
}

int fsl_config_globs_load(fsl_cx * f, fsl_list * li, char const * key){
  int rc = 0;
  char * val = NULL;
  if(!f || !li || !key || !*key) return FSL_RC_MISUSE;
  if(f->ckout.dir){
    /* Try versionable settings... */
    rc = fsl_config_get_versionable(f, key, &val);
    if(rc){
      rc = 0;
      assert(!val);
    }else{
      if(!val) return 0
                 /* Empty but existing list, so it trumps the
                    repo/global settings. */;
      else goto gotone;
    }
  }
  if(fsl_cx_db_repo(f)){
    /* See if the repo can serve us... */
    val = fsl_config_get_text(f, FSL_CONFDB_REPO, key, NULL);
    if(val) goto gotone;
    /* Else fall through and try global config... */
  }
  if(fsl_cx_db_config(f)){
    /*FIXME?: we arguably should open the global config for this if it
      is not already opened.*/
    val = fsl_config_get_text(f, FSL_CONFDB_GLOBAL, key, NULL);
    if(val) goto gotone;
  }
  gotone:
  if(val){
      rc = fsl_glob_list_parse( li, val );
      fsl_free(val);
      val = 0;
      return rc;
  }
  return rc;
}

#undef VERSIONABLE_FILENAME_PFMT
#undef SELECT_FROM_CONFIG
