Login
Artifact [032967c075]
Login

Artifact 032967c075385ec1c3b06584e456c249e35b7f38:


/* -*- 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 houses most of the context-related APIs.
*/
#include "fossil-scm/fsl_internal.h"
#include <assert.h>

#if defined(_WIN32)
# include <windows.h>
#else
# include <unistd.h> /* F_OK */
#endif

/* Only for debugging */
#include <stdio.h>
#define MARKER(pfexp)                                               \
  do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__);   \
    printf pfexp;                                                   \
  } while(0)


int fsl_cx_init( fsl_cx ** tgt, fsl_init_param * param ){
  static fsl_init_param paramDefaults = fsl_init_param_default_m;
  int rc = 0;
  fsl_cx * f;
  if(!tgt) return FSL_RC_MISUSE;
  else if(!param){
    if(!paramDefaults.output.state.state){
      paramDefaults.output.state.state = stdout;
    }
    param = &paramDefaults;
  }
  if(*tgt){
    f = *tgt;
    *f = fsl_cx_empty;
  }else{
    f = fsl_cx_malloc();
    if(!f) return FSL_RC_OOM;
    *tgt = f;
  }
  f->output = param->output;
  f->config = param->config;
  /* i'd prefer to use a temporary file here, because
     sqlite does not allow us to create TEMP views/tables
     in a non-main db. e.g.

     CREATE TEMPORARY VIEW foo.BAR ...

     is not legal.
  */;
  return rc;
}
    
void fsl_cx_reset(fsl_cx * f, char closeDatabases){
  if(!f) return;
  if(closeDatabases){
    fsl_repo_close(f);
    fsl_checkout_close(f);
    fsl_db_close(&f->dbConfig);
    fsl_buffer_clear(&f->dirCkout);
    assert(NULL==f->dbMain);
    assert(!f->dbRepo.dbh);
    assert(!f->dbCkout.dbh);
    assert(!f->dbConfig.dbh);
    assert(!f->dbRepo.filename.mem);
    assert(!f->dbCkout.filename.mem);
    assert(!f->dbConfig.filename.mem);
  }
  fsl_error_clean(&f->error);
  fsl_acache_clear(&f->cache.arty);
  fsl_buffer_clear(&f->scratch);
  fsl_id_bag_clear(&f->cache.mfSeen);
  fsl_id_bag_clear(&f->cache.leafCheck);
  while( f->cache.mf.head ){
    fsl_deck * next = f->cache.mf.head->next;
    f->cache.mf.head->next = NULL;
    fsl_deck_finalize(f->cache.mf.head);
    f->cache.mf.head = next;
  }
  f->cache = fsl_cx_empty.cache;
}

void fsl_cx_clear_mf_seen(fsl_cx * f){
  fsl_id_bag_clear(&f->cache.mfSeen);
}

void fsl_cx_finalize( fsl_cx * f ){
  void const * allocStamp = f ? f->allocStamp : NULL;
  if(!f) return;
  if(f->clientState.finalize.f){
    f->clientState.finalize.f( f->clientState.finalize.state,
                               f->clientState.state );
  }
  f->clientState = fsl_state_empty;
  fsl_cx_reset(f, 1);
  if(f->output.state.finalize.f){
    f->output.state.finalize.f( f->output.state.finalize.state,
                                f->output.state.state );
  }
  f->output = fsl_outputer_empty;
  *f = fsl_cx_empty;
  if(&fsl_cx_empty == allocStamp){
    fsl_free(f);
  }else{
    f->allocStamp = allocStamp;
  }
}

void fsl_cx_err_clear(fsl_cx * f){
  if(f && f->error.msg.mem){
    fsl_error_clean(&f->error);
  }
}

int fsl_cx_err_set_e( fsl_cx * f, fsl_error * err ){
  if(!f) return FSL_RC_MISUSE;
  else if(!err){
    return fsl_cx_err_set(f, 0, NULL);
  }else{
    fsl_error_move(err, &f->error);
    fsl_error_clean(err);
    return f->error.code;
  }
}

int fsl_cx_err_setv( fsl_cx * f, int code, char const * fmt,
                     va_list args ){
  return f
    ? fsl_error_setv( &f->error, code, fmt, args )
    : FSL_RC_MISUSE;
}

int fsl_cx_err_set( fsl_cx * f, int code, char const * fmt,
                    ... ){
  if(!f) return FSL_RC_MISUSE;
  else{
    int rc;
    va_list args;
    va_start(args,fmt);
    rc = fsl_error_setv( &f->error, code, fmt, args );
    va_end(args);
    return rc;
  }
}

int fsl_cx_err_get( fsl_cx * f, char const ** str, fsl_size_t * len ){
  return f
    ? fsl_error_get( &f->error, str, len )
    : FSL_RC_MISUSE;
}

fsl_id_t fsl_cx_last_insert_id(fsl_cx * f){
  return (f && f->dbMain && f->dbMain->dbh)
    ? fsl_db_last_insert_id(f->dbMain)
    : -1;
}

fsl_cx * fsl_cx_malloc(){
  fsl_cx * rc = (fsl_cx *)fsl_malloc(sizeof(fsl_cx));
  if(rc) {
    *rc = fsl_cx_empty;
    rc->allocStamp = &fsl_cx_empty;
  }
  return rc;
}

int fsl_cx_err_report( fsl_cx * f, char addNewline ){
  if(!f) return FSL_RC_MISUSE;
  else if(f->error.code){
    char const * msg = f->error.msg.used
      ? (char const *)f->error.msg.mem
      : fsl_rc_cstr(f->error.code)
      ;
    return fsl_outputf(f, "Error #%d: %s%s",
                       f->error.code, msg,
                       addNewline ? "\n" : "");
  }
  else return 0;
}

int fsl_cx_uplift_db_error( fsl_cx * f, fsl_db * db ){
  assert(f);
  if(!f) return FSL_RC_MISUSE;
  if(!db){
    db = f->dbMain;
    if(!db) return FSL_RC_MISUSE;
  }
  fsl_error_move( &db->error, &f->error );
  return f->error.code;
}


fsl_db * fsl_cx_db_config( fsl_cx * f ){
  if(!f) return NULL;
  else if(f->dbConfig.dbh) return &f->dbConfig;
  else if(f->dbMain && (FSL_DB_ROLE_CONFIG & f->dbMain->role)) return f->dbMain;
  else return NULL;
}

fsl_db * fsl_cx_db_repo( fsl_cx * f ){
  if(!f) return NULL;
  else if(f->dbRepo.dbh) return &f->dbRepo;
  else if(f->dbMain && (FSL_DB_ROLE_REPO & f->dbMain->role)) return f->dbMain;
  else return NULL;
}

fsl_db * fsl_cx_db_checkout( fsl_cx * f ){
  if(!f) return NULL;
  else if(f->dbCkout.dbh) return &f->dbCkout;
  else if(f->dbMain && (FSL_DB_ROLE_CHECKOUT & f->dbMain->role)) return f->dbMain;
  else return NULL;
}

fsl_db * fsl_cx_db( fsl_cx * f ){
  return f ? f->dbMain : NULL;
}




/**
 ** Returns one of f->db{Config,Repo,Ckout,Main}
 ** or NULL.
 */
static fsl_db * fsl_cx_db_for_role(fsl_cx * f, fsl_db_role_t r){
  fsl_db * db = NULL;
  switch(r){
    case FSL_DB_ROLE_CONFIG:
      db = &f->dbConfig;
      break;
    case FSL_DB_ROLE_REPO:
      db = &f->dbRepo;
      break;
    case FSL_DB_ROLE_CHECKOUT:
      db = &f->dbCkout;
      break;
    case FSL_DB_ROLE_MAIN:
      db = f->dbMain;
      break;
    case FSL_DB_ROLE_NONE:
    default:
      break;
  }
  return db;
}

static int fsl_cx_attach_role(fsl_cx * f, const char *zDbName, fsl_db_role_t r){
  char const * label = fsl_db_role_label(r);
  fsl_db * db = fsl_cx_db_for_role(f, r);
  fsl_buffer * nameBuf = db ? &db->filename : NULL;
  int rc;
  if(!f->dbMain){
    assert(!"Misuse: f->dbMain has not been set: cannot attach role.");
    return FSL_RC_MISUSE;
  }
  else if(!nameBuf) return FSL_RC_RANGE;
  else if(r & f->dbMain->role){
    assert(!"Misuse: role is already attached.");
    return FSL_RC_ACCESS;
  }
  /* assert(db); */
  assert(label);
  switch(r){
    case FSL_DB_ROLE_CONFIG:
    case FSL_DB_ROLE_REPO:
    case FSL_DB_ROLE_CHECKOUT:
      break;
    case FSL_DB_ROLE_MAIN:
    case FSL_DB_ROLE_NONE:
    default:
      assert(!"cannot happen/not legal");
      return FSL_RC_RANGE;
  }
  rc = fsl_db_attach(f->dbMain, zDbName, label);
  nameBuf->used = 0;
  if(rc){
    fsl_cx_uplift_db_error(f, NULL);
  }else{
    f->dbMain->role |= r;
    fsl_buffer_append(nameBuf, zDbName, -1);
  }
  return rc;
}


/**
 ** Deattaches the given db role from f->dbMain and removes the role
 ** from f->dbMain->role. 
 */
static int fsl_cx_detach_role(fsl_cx * f, fsl_db_role_t r){
  if(!f || !f->dbMain) return FSL_RC_MISUSE;
  else if(!(r & f->dbMain->role)){
    assert(!"Misuse: cannot detach unattached role.");
    return FSL_RC_NOT_FOUND;
  }
  else{
    fsl_db * db = fsl_cx_db_for_role(f,r);
    int rc;
    if(!db) return FSL_RC_RANGE;
    assert(f->dbMain != db);
    f->dbMain->role &= ~r;
    rc = fsl_db_detach( f->dbMain, fsl_db_role_label(r) );
    return rc;
  }
}


/*
** Analog to v1's db_open_or_attach().
**
** This function is still very much up for reconsideration. i'm not
** terribly happy with the "db roles" here - i'd prefer to have
** them each in their own struct, but understand that the roles may
** play a part in query generation, so i haven't yet ruled
** them out.
**
** Opens or attaches the given db file. zDbName is the
** file name of the DB. role is the role of that db
** in the framework (that determines its db name in SQL).
**
** The first time this is called, f->dbMain is set to point
** to fsl_cx_db_for_role(f, role).
**
** If f->dbMain is already set then the db is attached using a
** role-dependent name and role is added to f->dbMain->role's bitmask.
**
** If f->dbMain is not open then it is opened here and its role is set
** to the given role.
**
** the given db zDbName is the name of a database file.  If f->dbMain is not opened
** then that db is assigned to the given db file, otherwise
** attach attach zDbName using the name zLabel.
**
** If pWasAttached is not NULL then it is set to a true value if the
** db gets attached, else a false value. It is only modified on success.
**
** It is an error to open the same db role more than once, and trying
** to do so results in a FSL_RC_ACCESS error (f's error state is
** updated).
*/
static int fsl_cx_db_main_open_or_attach( fsl_cx * f,
                                          const char *zDbName,
                                          /* const char *zLabel, */
                                          fsl_db_role_t role,
                                          int *pWasAttached){
  int rc;
  int wasAttached = 0;
  fsl_db * db;
  assert(f);
  assert(zDbName && *zDbName);
  assert((FSL_DB_ROLE_CONFIG==role)
         || (FSL_DB_ROLE_CHECKOUT==role)
         || (FSL_DB_ROLE_REPO==role));
  if(!f || !zDbName) return FSL_RC_MISUSE;
  else if((FSL_DB_ROLE_NONE==role) || !*zDbName) return FSL_RC_RANGE;
  switch(role){
    case FSL_DB_ROLE_REPO:
      db = &f->dbRepo;
      break;
    case FSL_DB_ROLE_CHECKOUT:
      db = &f->dbCkout;
      break;
    case FSL_DB_ROLE_CONFIG:
      db = &f->dbConfig;
      break;
    default:
      assert(!"not possible");
      db = NULL;
      /* We'll fall through and segfault in a moment... */
  }
  assert(db);
  try_again:
  if(!f->dbMain) {
    assert( FSL_DB_ROLE_NONE==db->role );
    assert( NULL==db->dbh );
    f->dbMain = db;
    db->f = f;
    rc = fsl_db_open( db, zDbName, FSL_OPEN_F_RW );
    if(!rc) db->role = role;
    /* MARKER(("db->role=%d\n",db->role)); */
    /* g.db = fsl_db_open(zDbName); */
    /* g.zMainDbType = zLabel; */
    /* f->mainDbType = zLabel; */
    /* f->dbMain.role = FSL_DB_ROLE_MAIN; */
  }else{
    assert( FSL_DB_ROLE_NONE!=f->dbMain->role );
    assert( f == f->dbMain->f );
    if(NULL == f->dbMain->dbh){
      /* It was closed in the meantime. Re-assign dbMain... */
      MARKER(("Untested: re-assigning f->dbMain after it has been closed.\n"));
      f->dbMain = NULL;
      goto try_again;
    }
    if(role == db->role){
      return fsl_cx_err_set(f, FSL_RC_ACCESS,
                            "Cannot open/attach db role "
                            "#%d (%s) more than once.",
                            role, fsl_db_role_label(role));
    }
    rc = fsl_cx_attach_role(f, zDbName, role);
    wasAttached = 1;
  }
  if(!rc){
    if(pWasAttached) *pWasAttached = wasAttached;
  }else{
    if(db->error.code){
      fsl_cx_uplift_db_error(f, db);
    }
    if(db==f->dbMain) f->dbMain = NULL;
    fsl_db_close(db);
  }
  return rc;
}

/*
** If zDbName is a valid checkout database file, open it and return 0.
** If it is not a valid local database file, return a non-0 code.
*/
static int fsl_cx_checkout_open_db(fsl_cx * f, const char *zDbName){
  fsl_size_t lsize;
  /* char *zVFileDef; */
  int rc;
  lsize = fsl_file_size(zDbName);
  if( -1 == lsize  ){
    return FSL_RC_NOT_FOUND /* FSL_RC_ACCESS? */;
  }
  if( lsize%1024!=0 || lsize<4096 ) return FSL_RC_RANGE;
#if 0
  rc = fsl_cx_attach_role(f, zDbName, FSL_DB_ROLE_CHECKOUT);
#else
  rc = fsl_cx_db_main_open_or_attach(f, zDbName,
                                     FSL_DB_ROLE_CHECKOUT,
                                     NULL);
#endif
  if(rc){
    fsl_cx_uplift_db_error(f, NULL);
  }
  return rc;
#if 0
  /* Historical bits: no longer needed(?). */

  zVFileDef = fsl_db_text(0, "SELECT sql FROM %s.sqlite_master"
                          " WHERE name=='vfile'",
                          fsl_cx_db_name("localdb")); /* ==> "ckout" */
  if( zVFileDef==0 ) return 0;

  /* If the "isexe" column is missing from the vfile table, then
  ** add it now.   This code added on 2010-03-06.  After all users have
  ** upgraded, this code can be safely deleted.
  */
  if( !fsl_str_glob("* isexe *", zVFileDef) ){
    fsl_db_multi_exec("ALTER TABLE vfile ADD COLUMN isexe BOOLEAN DEFAULT 0");
  }

  /* If "islink"/"isLink" columns are missing from tables, then
  ** add them now.   This code added on 2011-01-17 and 2011-08-27.
  ** After all users have upgraded, this code can be safely deleted.
  */
  if( !fsl_str_glob("* islink *", zVFileDef) ){
    db_multi_exec("ALTER TABLE vfile ADD COLUMN islink BOOLEAN DEFAULT 0");
    if( db_local_table_exists_but_lacks_column("stashfile", "isLink") ){
      db_multi_exec("ALTER TABLE stashfile ADD COLUMN isLink BOOL DEFAULT 0");
    }
    if( db_local_table_exists_but_lacks_column("undo", "isLink") ){
      db_multi_exec("ALTER TABLE undo ADD COLUMN isLink BOOLEAN DEFAULT 0");
    }
    if( db_local_table_exists_but_lacks_column("undo_vfile", "islink") ){
      db_multi_exec("ALTER TABLE undo_vfile ADD COLUMN islink BOOL DEFAULT 0");
    }
  }
#endif
  return 0;
}


int fsl_repo_close( fsl_cx * f ){
  if(!f) return FSL_RC_MISUSE;
  else{
    int rc;
    fsl_db * db = &f->dbRepo;
    if(db->dbh){
      rc = fsl_db_close(db);
    }else if(f->dbMain && (FSL_DB_ROLE_REPO & f->dbMain->role)){
      assert(f->dbMain!=db);
      rc = fsl_cx_detach_role(f, FSL_DB_ROLE_REPO);
    }
    else rc = FSL_RC_NOT_FOUND;
    assert(!db->dbh);
    if(f->dbMain==db) f->dbMain = NULL;
    fsl_db_clear_strings(db, 1);
    return rc;
  }
}

int fsl_config_close( fsl_cx * f ){
  if(!f) return FSL_RC_MISUSE;
  else{
    int rc;
    fsl_db * db = &f->dbConfig;
    if(db->dbh){
      rc = fsl_db_close(db);
    }else if(f->dbMain && (FSL_DB_ROLE_CONFIG & f->dbMain->role)){
      assert(f->dbMain!=db);
      rc = fsl_cx_detach_role(f, FSL_DB_ROLE_CONFIG);
    }
    else rc = FSL_RC_NOT_FOUND;
    assert(!db->dbh);
    if(f->dbMain==db) f->dbMain = NULL;
    fsl_db_clear_strings(db, 1);
    return rc;
  }
}

int fsl_checkout_close( fsl_cx * f ){
  if(!f) return FSL_RC_MISUSE;
  else{
    int rc;
    fsl_db * db = &f->dbCkout;
#if 0
    fsl_repo_close(f)
        /*
          Ignore error - it might not be opened.  Close it first
          because it was (if things went normally) attached to
          f->dbCkout resp. HOWEVER: we have a bug-in-waiting here,
          potentially. If repo becomes the main db for an attached
          checkout (which isn't current possibly because opening a
          checkout closes/(re)opens the repo) then we stomp on our db
          handle here. Hypothetically.

          If the repo is dbMain:

          a) we cannot have a checkout because fsl_repo_open_db()
          fails if a repo is already opened.

          b) fsl_repo_close() will clear f->dbMain,
          leaving the code below to return FSL_RC_NOT_FOUND,
          which would be misleading.

          But that's all hypothetical - best we make it impossible
          for the public API to have a checkout without a repo
          or a repo/checkout mismatch.
        */
        ;
#endif
    if(db->dbh){
      assert(!f->dbRepo.dbh);
      fsl_repo_close(f);
      rc = fsl_db_close(db);
    }
    else if(f->dbMain && (FSL_DB_ROLE_CHECKOUT & f->dbMain->role)){
      assert(f->dbMain!=db);
      assert(f->dbRepo.dbh
             && "This will only hold until config is used as a main db.");
      assert((&f->dbRepo == f->dbMain) && "Same thing here.");
      rc = fsl_cx_detach_role(f, FSL_DB_ROLE_CHECKOUT);
      fsl_repo_close(f);
    }
    else{
      /* fsl_repo_close(f); */
      rc = FSL_RC_NOT_FOUND;
    }
    assert(!db->dbh);
    if(f->dbMain==db) f->dbMain = NULL;
    fsl_db_clear_strings(db, 1);
    return rc;
  }
}

int fsl_cx_prepare( fsl_cx *f, fsl_stmt * tgt, char const * sql,
                      ... ){
  if(!f || !f->dbMain) return FSL_RC_MISUSE;
  else{
    int rc;
    va_list args;
    va_start(args,sql);
    rc = fsl_db_preparev( f->dbMain, tgt, sql, args );
    va_end(args);
    return rc;
  }
}

int fsl_cx_preparev( fsl_cx *f, fsl_stmt * tgt, char const * sql,
                       va_list args ){
  return (f && f->dbMain && tgt)
    ? fsl_db_preparev(f->dbMain, tgt, sql, args)
    : FSL_RC_MISUSE;
}

int fsl_config_open( fsl_cx * f, char const * openDbName ){
  int rc = 0;
  char const *zDbName = NULL;
  fsl_buffer dbPath = fsl_buffer_empty;
  char useAttach = 0; /* TODO? Move this into a parameter? Do we need the v1 behaviour? */
  if(!f) return FSL_RC_MISUSE;
  else if(f->dbConfig.dbh){
    /* fsl_db_close(&f->dbConfig); */
    /* fsl_cx_detach_role(f, FSL_DB_ROLE_CONFIG); */
    fsl_config_close(f);
  }

  if(openDbName && *openDbName){
    zDbName = openDbName;
  }else{
    rc = fsl_find_home_dir( &dbPath, 1 );
    if(!rc){
      char const * dbName =
#if defined(_WIN32) || defined(__CYGWIN__)
        /* . filenames give some window systems problems and many apps problems */
        "_fossil"
#else
        ".fossil"
#endif
        ;
      rc = fsl_buffer_appendf(&dbPath, "/%s", dbName);
      if(rc){
        rc = fsl_cx_err_set(f, rc, "%.*s", (int)dbPath.used, fsl_buffer_cstr(&dbPath));
        fsl_buffer_reserve(&dbPath, 0);
        return rc;
      }
    }
    assert(dbPath.used);
    zDbName = fsl_buffer_cstr(&dbPath);
  }

#if 0
  if( fsl_file_size(zDbName)<1024*3 ){
    /* TODO: port in db init code. Consider doing the
       init AFTER opening it, so that we can pipe the
       input into fsl_db_multi_exec(). The v1 approach
       opens the config db twice for this case.

       We need to move away from v1's habit of holding the
       schema as a large C-string because C does not guaranty
       that unbounded sizes are legal (and fossil regularly
       exceeds that limit).

       http://stackoverflow.com/questions/11488616/why-is-max-length-of-c-string-literal-different-from-max-char
       
       http://msdn.microsoft.com/en-us/library/sx08afx2.aspx

       Says:
       
       "ANSI compatibility requires a compiler to accept up to 509
       characters in a string literal after concatenation."

       Supposedly (see first link) C99 increases that minimum to 4k.

       Instead, we can store the schema in an SQL file and compile it
       in as a static const char[].

       Or... probably simpler... we pack the SQL into

       static const char[][] = { "...", "...", "..." }

       and hope we don't hit the limit for individual chunks, e.g.
       CREATE TABLE parts?
    */
    db_init_database(zDbName, zConfigSchema, (char*)0);
  }
#endif

  
#if defined(_WIN32) || defined(__CYGWIN__)
  if( fsl_file_access(zDbName, W_OK) ){
    rc = fsl_cx_err_set(f, FSL_RC_ACCESS,
                        "Configuration database [%s] "
                        "must be writeable.", zDbName);
    fsl_buffer_reserve(&dbPath,0);
    return rc;
  }
#endif


#if 0
  rc = fsl_cx_attach_role(f, zDbName, FSL_DB_ROLE_CONFIG);
#else
  if( useAttach ){
    rc = fsl_cx_attach_role(f, zDbName, FSL_DB_ROLE_CONFIG);
  }else{
    rc = fsl_cx_db_main_open_or_attach(f, zDbName,
                                       FSL_DB_ROLE_CONFIG,
                                       NULL);
    /* rc = fsl_db_open(&f->dbConfig, zDbName, FSL_OPEN_F_RWC ); */
  }
#endif
  fsl_buffer_reserve(&dbPath,0);
  /* g.zConfigDbName = zDbName; */
  if(!rc && !f->dbMain){
    f->dbMain = &f->dbConfig;
  }
  return rc;
}

int fsl_repo_open_db( fsl_cx * f, char const * repoDbFile,
                      int openFlags ){
  if(!f || !repoDbFile || !*repoDbFile) return FSL_RC_MISUSE;
  else if(f->dbRepo.dbh){
    return fsl_cx_err_set(f, FSL_RC_ACCESS,
                          "Context already has an opened repository.");
  }
  else {
    int rc;
    /* fsl_db * db = &f->dbRepo; */
    /* db->f = f; */
    if(0!=fsl_file_access( repoDbFile, F_OK )){
      return fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                            "Repository db [%s] not found.",
                            repoDbFile);
    }
    if(!openFlags){
      openFlags = FSL_OPEN_F_RW | FSL_OPEN_F_SCHEMA_VALIDATE;
    }
    /* fsl_cx_err_set(f, 0, NULL); */
    /* rc = fsl_db_open( db, repoDbFile, openFlags ); */
#if 0
    rc = fsl_cx_attach_role(f, repoDbFile, FSL_DB_ROLE_REPO);
#else
    rc = fsl_cx_db_main_open_or_attach(f, repoDbFile,
                                       FSL_DB_ROLE_REPO,
                                       NULL);
#endif
    return rc;
  }
}

/**
 ** Tries to open the repository which from which the current checkout
 ** derives.  Returns 0 on success.
 */
static int fsl_repo_open_for_checkout(fsl_cx * f){
  char * repoDb = NULL;
  int rc;
  fsl_buffer nameBuf = fsl_buffer_empty;
  assert(f);
  assert(f->dirCkout.used);
  rc = fsl_db_get_text(&f->dbCkout, &repoDb, NULL,
                       "SELECT %B || '/' || value FROM vvar "
                       "WHERE name='repository'",
                       &f->dirCkout);

  if(rc) fsl_cx_uplift_db_error( f, NULL );
  else if(repoDb){
    rc = fsl_file_canonical_name(repoDb, &nameBuf, 0);
    fsl_free(repoDb);
    if(!rc){
      repoDb = fsl_buffer_str(&nameBuf);
      assert(repoDb);
      rc = fsl_repo_open_db(f, repoDb, 0);
    }
    fsl_buffer_reserve(&nameBuf, 0);
  }else{
    /* This can only happen if we are not using a proper
       checkout db or someone has removed the repo link.
    */
    rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                        "Could not determine this checkout's "
                        "repository db file.");
  }
  return rc;
}

int fsl_checkout_open( fsl_cx * f, char const * dirName,
                       fsl_int_t dirNameLen ){
  int rc;
  fsl_int_t dLen = 0, i;
  static const char aDbName[][10] = { "_FOSSIL_", ".fslckout" };
  fsl_buffer * buf = &f->scratch
    /*FIXME: don't use f->scratch here. Fix the returns below to
      account for a local buffer. */
    ;
  enum { DbCount = 2 };
  if(fsl_cx_db_checkout(f)) return FSL_RC_ACCESS;
  buf->used = 0;
  if(dirName && (dirNameLen<0)) dirNameLen = fsl_strlen(dirName);
  if(dirName){
    if((0==dirNameLen) || !*dirName) return FSL_RC_RANGE;
    rc = fsl_buffer_append( buf, dirName, dirNameLen );
    if(rc) return fsl_cx_err_set(f, rc, NULL)
      /* Reminder: fsl_error_set() ignores the error string (does not
         allocate) if code==FSL_RC_OOM.
      */
      ;
    dLen = dirNameLen;
  }else{
    char zPwd[2000];
    fsl_size_t pwdLen = 0;
    rc = fsl_getcwd( zPwd, sizeof(zPwd)/sizeof(zPwd[0]), &pwdLen );
    if(rc){
      return fsl_cx_err_set(f, rc,
                            "Could not determine current directory. "
                            "Error code %d (%s).",
                            rc, fsl_rc_cstr(rc));
    }
    if(1 == dLen && '/'==*zPwd) *zPwd = '.'
      /* When in the root directory (or chroot) then change dir name
         name to something we can use.
      */
      ;
    rc = fsl_buffer_append(buf, zPwd, dLen);
    if(rc) return fsl_cx_err_set(f, rc, NULL);
    dLen = (fsl_int_t)pwdLen;
  }
  if(rc) return fsl_cx_err_set(f, rc, NULL);
  assert(buf->used == dLen);
  assert(buf->capacity>buf->used);
  assert(0==buf->mem[dLen]);
  while(dLen>0){
    /*
      Loop over the list in aDbName, appending each one to
      the dir name in the search for something we can use.
    */
    fsl_int_t lenMarker = dLen /* position to
                                  re-set to on each
                                  sub-iteration. */ ;
    for( i = 0; i < DbCount; ++i ){
      buf->used = (fsl_size_t)lenMarker;
      dLen = lenMarker;
      rc = fsl_buffer_appendf( buf, "/%s", aDbName[i]);
      if(rc) return fsl_cx_err_set(f, rc, NULL);
      rc = fsl_cx_checkout_open_db(f, fsl_buffer_cstr(buf));
      if(rc){
        --dLen;
        while( dLen>0 && buf->mem[dLen]!='/' ){ --dLen; }
        while( dLen>0 && buf->mem[dLen-1]=='/' ){ --dLen; }
        if(dLen>lenMarker){
          buf->mem[dLen] = 0;
        }
      }
      else{
        f->dirCkout.used = 0;
        rc = fsl_buffer_append(&f->dirCkout, buf->mem, (fsl_size_t)lenMarker);
        if(rc) return fsl_cx_err_set(f, rc, NULL);
        /* why?
           rc = fsl_config_open(f, NULL);

           Seems to me we don't need that at the library level. In my
           experience global config options for a lib which is used by
           multiple apps are generally a bad idea, as no single
           setting of any given property will be best for all apps.
           Fossil's global config db assumes that there is only one
           fossil-based app, which is no longer the case.
        */
        rc = fsl_repo_open_for_checkout(f);
        return rc;
      }
    }
  }
  buf->used = 0;
  return fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                        "Could not find checkout under [%.*s].",
                        dirName ? (int)dirNameLen : (int)1,
                        dirName ? dirName : ".");
}


char const * fsl_cx_db_file_for_role(fsl_cx const * f,
                                     fsl_db_role_t r,
                                     fsl_size_t * len){
  fsl_db const * db = fsl_cx_db_for_role((fsl_cx*)f, r);
  fsl_buffer const * b = db ? &db->filename : NULL;
  return b ? fsl_buffer_cstr2(b, len) : NULL;
}

char const * fsl_cx_config_db_file(fsl_cx const * f,
                                   fsl_size_t * len){
  char const * rc = NULL;
  if(f && f->dbConfig.filename.used){
    rc = fsl_buffer_cstr2(&f->dbConfig.filename, len);
  }
  return rc;
}

char const * fsl_repo_db_file(fsl_cx const * f,
                                 fsl_size_t * len){
  char const * rc = NULL;
  if(f && f->dbRepo.filename.used){
    rc = fsl_buffer_cstr2(&f->dbRepo.filename, len);
  }
  return rc;
}

char const * fsl_cx_checkout_db_file(fsl_cx const * f,
                                     fsl_size_t * len){
  char const * rc = NULL;
  if(f && f->dbCkout.filename.used){
    rc = fsl_buffer_cstr2(&f->dbCkout.filename, len);
  }
  return rc;
}

char const * fsl_cx_checkout_dir_name(fsl_cx const * f,
                                      fsl_size_t * len){
  char const * rc = NULL;
  if(f && f->dirCkout.used){
    rc = fsl_buffer_cstr2(&f->dirCkout, len);
  }
  return rc;
}


#undef MARKER