/* -*- 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/fossil-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_cx_init_opt const * param ){ static fsl_cx_init_opt paramDefaults = fsl_cx_init_opt_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 = ¶mDefaults; } 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; 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_free(f->cache.user); f->cache.user = NULL; 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); fsl_id_bag_clear(&f->cache.toVerify); 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->xlinkers.list){ #if 0 /* Potential TODO: add client-specified finalizer for xlink callback state, using a fsl_state to replace the current (void*) for x->state. Seems like overkill for the time being. */ fsl_size_t i; for( i = 0; i < f->xlinkers.used; ++i ){ fsl_xlinker * x = f->xlinkers.list + i; if(x->state.finalize.f){ x->state.finalize.f(x->state.finalize.state, x->state.state); } } #endif fsl_free(f->xlinkers.list); f->xlinkers = fsl_xlinker_list_empty; } 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_needs_repo(fsl_cx * f){ fsl_db * db = f ? fsl_cx_db_repo(f) : NULL; if(f && !db) fsl_cx_err_set(f, FSL_RC_NOT_A_REPO, "Fossil has no opened repository db."); return db; } 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() 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( fsl_cx * f, char const * repoDbFile ){ if(!f || !repoDbFile || !*repoDbFile) return FSL_RC_MISUSE; else if(fsl_cx_db_repo(f)){ return fsl_cx_err_set(f, FSL_RC_ACCESS, "Context already has an opened repository."); } else { int rc; if(0!=fsl_file_access( repoDbFile, F_OK )){ rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND, "Repository db [%s] not found.", repoDbFile); }else{ rc = fsl_cx_db_main_open_or_attach(f, repoDbFile, FSL_DB_ROLE_REPO, NULL); } 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(f, repoDb); } 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; } int fsl_cx_flags_get( fsl_cx * f ){ return f ? f->flags : -1; } int fsl_cx_flag_set( fsl_cx * f, int flags, char enable ){ if(!f) return -1; else { if(enable) f->flags |= flags; else f->flags &= ~flags; return f->flags; } } fsl_xlinker * fsl_xlinker_by_name( fsl_cx * f, char const * name ){ fsl_xlinker * rv = NULL; fsl_size_t i; for( i = 0; i < f->xlinkers.used; ++i ){ rv = f->xlinkers.list + i; if(0==fsl_strcmp(rv->name, name)) return rv; } return NULL; } int fsl_xlink_listener( fsl_cx * f, char const * name, fsl_deck_xlink_f cb, void * cbState ){ fsl_xlinker * x; if(!f || !cb || !name) return FSL_RC_MISUSE; x = fsl_xlinker_by_name(f, name); if(x){ /* Replace existing entry */ x->f = cb; x->state = cbState; return 0; } else if(f->xlinkers.used <= f->xlinkers.capacity){ /* Expand the array */ fsl_size_t const n = f->xlinkers.used ? f->xlinkers.used * 2 : 5; fsl_xlinker * re = (fsl_xlinker *)fsl_realloc(f->xlinkers.list, n * sizeof(fsl_xlinker)); if(!re) return FSL_RC_OOM; f->xlinkers.list = re; } x = f->xlinkers.list + f->xlinkers.used++; *x = fsl_xlinker_empty; x->f = cb; x->state = cbState; return 0; } int fsl_cx_user_set( fsl_cx * f, char const * userName ){ if(!f) return FSL_RC_MISUSE; else if(!userName){ fsl_free(f->cache.user); f->cache.user = NULL; return 0; }else{ char * u = fsl_strdup(userName); if(!u) return FSL_RC_OOM; else{ fsl_free(f->cache.user); f->cache.user = u; return 0; } } } char const * fsl_cx_user_get( fsl_cx const * f ){ return f ? f->cache.user : NULL; } #undef MARKER