Login
Artifact [e71d2c1719]
Login

Artifact e71d2c1719b2753743d26588106832b5cba426c9:


/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#if !defined(ORG_FOSSIL_SCM_FSL_INTERNAL_H_INCLUDED)
#define ORG_FOSSIL_SCM_FSL_INTERNAL_H_INCLUDED
/*
  Copyright 2013-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt

  SPDX-License-Identifier: BSD-2-Clause-FreeBSD
  SPDX-FileCopyrightText: 2021 The Libfossil Authors
  SPDX-ArtifactOfProjectName: Libfossil
  SPDX-FileType: Code

  Heavily indebted to the Fossil SCM project (https://fossil-scm.org).

  *****************************************************************************
  This file declares library-level internal APIs which are shared
  across the library.
*/

#include "repo.h" /* a fossil-xxx header MUST come first b/c of config macros */

#if defined(__cplusplus)
extern "C" {
#endif

typedef struct fsl__bccache fsl__bccache;
typedef struct fsl__bccache_line fsl__bccache_line;
typedef struct fsl__pq fsl__pq;
typedef struct fsl__pq_entry fsl__pq_entry;

/** @internal

    Queue entry type for the fsl__pq class.

    Potential TODO: we don't currently use the (data) member. We can
    probably remove it.
*/
struct fsl__pq_entry {
  /** RID of the entry. */
  fsl_id_t id;
  /** Raw data associated with this entry. */
  void * data;
  /** Priority of this element. */
  double priority;
};
/** @internal
    Empty-initialized fsl__pq_entry structure.
*/
#define fsl__pq_entry_empty_m {0,NULL,0.0}

/** @internal

    A simple priority queue class. Instances _must_ be initialized
    by copying fsl__pq_empty or fsl__pq_empty_m (depending on where
    the instance lives).
*/
struct fsl__pq {
  /** Number of items allocated in this->list. */
  uint16_t capacity;
  /** Number of items used in this->list. */
  uint16_t used;
  /** The queue. It is kept sorted by entry->priority. */
  fsl__pq_entry * list;
};

/** @internal
    Empty-initialized fsl__pq struct, intended for const-copy initialization.
*/
#define fsl__pq_empty_m {0,0,NULL}

/** @internal
    Empty-initialized fsl__pq struct, intended for copy initialization.
*/
extern  const fsl__pq fsl__pq_empty;

/** @internal

    Clears the contents of p, freeing any memory it owns, but not
    freeing p. Results are undefined if !p.
*/
void fsl__pq_clear(fsl__pq * p);

/** @internal

    Insert element e into the queue. Returns 0 on success, FSL_RC_OOM
    on error. Results are undefined if !p. pData may be NULL.
*/
int fsl__pq_insert(fsl__pq *p, fsl_id_t e,
                  double v, void *pData);

/** @internal

    Extracts (removes) the first element from the queue (the element
    with the smallest value) and return its ID.  Return 0 if the queue
    is empty. If pp is not NULL then *pp is (on success) assigned to
    opaquedata pointer mapped to the entry.
*/
fsl_id_t fsl__pq_extract(fsl__pq *p, void **pp);

/** @internal

    Holds one "line" of a fsl__bccache cache.
*/
struct fsl__bccache_line {
  /**
     RID of the cached record.
  */
  fsl_id_t rid;
  /**
     Age. Newer is larger.
  */
  fsl_uint_t age;
  /**
     Content of the artifact.
  */
  fsl_buffer content;
};
/** @internal

    Empty-initialized fsl__bccache_line structure.
*/
#define fsl__bccache_line_empty_m { 0,0,fsl_buffer_empty_m }


/** @internal

    A cache for tracking the existence of blobs while the internal
    goings-on of fsl_content_get() and friends are going on.

    "bc" ==> blob cache. 

    Historically fossil caches artifacts as their blob content, but
    libfossil will likely (at some point) to instead cache fsl_deck
    instances, which contain all of the same data in pre-parsed form.
    It cost more memory, though. That approach also precludes caching
    non-structural artifacts (i.e. opaque client blobs).

    Potential TODO: the limits of the cache size are currently
    hard-coded and changing them "by hand" won't have much effect.
    We should possibly have an API to tweak these limits.
*/
struct fsl__bccache {
  /**
     Total amount of buffer memory (in bytes) used by cached content.
     This does not account for memory held by this->list.
  */
  unsigned szTotal;
  /**
     Limit on the (approx.) amount of memory (in bytes) which can be
     taken up by the cached buffers at one time. Fossil's historical
     value is 50M.
  */
  unsigned szLimit;
  /**
     Number of entries "used" in this->list.
  */
  uint16_t used;
  /**
     Approximate upper limit on the number of entries in this->list.
     This limit may be violated slightly.

     This list gets searched linearly so this number should ideally be
     relatively small: 3 digits or less. Fossil's historical value is
     500.
  */
  uint16_t usedLimit;
  /**
     Number of allocated slots in this->list.
  */
  uint16_t capacity;
  /**
     Next cache counter age. Higher is newer.
  */
  fsl_uint_t nextAge;
  /**
     List of cached content, ordered by age.
  */
  fsl__bccache_line * list;
  /**
     RIDs of all artifacts currently in the this->list
     cache.
  */
  fsl_id_bag inCache;
  /**
     RIDs of known-missing content.
  */
  fsl_id_bag missing;
  /**
     RIDs of known-existing content (not necessarily in
     the cache).
  */
  fsl_id_bag available;
  /**
     Metrics solely for internal use in looking for
     optimizations. These are only updated by fsl_content_get().
  */
  struct {
    unsigned hits;
    unsigned misses;
  } metrics;
};
/** @internal

    Empty-initialized fsl__bccache structure, intended
    for const-copy initialization.
*/
#define fsl__bccache_empty_m {                \
  0U/*szTotal*/,                             \
  20000000U/*szLimit. Historical fossil value=50M*/, \
  0U/*used*/,300U/*usedLimit. Historical fossil value=500*/,\
  0/*capacity*/,                            \
  0/*nextAge*/,NULL/*list*/,                \
  fsl_id_bag_empty_m/*inCache*/,            \
  fsl_id_bag_empty_m/*missing*/,            \
  fsl_id_bag_empty_m/*available*/,        \
  {/*metrics*/ 0U/*hits*/,0U/*misses*/} \
}
/** @internal

    Empty-initialized fsl__bccache structure, intended
    for copy initialization.
*/
extern const fsl__bccache fsl__bccache_empty;

/** @internal

   Very internal.

   "Manifest cache" for fsl_deck entries encountered during
   crosslinking. This type is intended only to be embedded in fsl_cx.

   The routines for managing this cache are static in deck.c:
   fsl__cx_mcache_insert() and fsl__cx_mcache_search().

   The array members in this struct MUST have the same length
   or results are undefined.
*/
struct fsl__mcache {
  /** Next age value. No clue how the cache will react once this
      overflows. */
  fsl_uint_t nextAge;
  /** Counts the number of cache hits. */
  unsigned hits;
  /** Counts the number of cache misses. */
  unsigned misses;
  /** The virtual age of each deck in the cache. They get evicted
      oldest first. */
  fsl_uint_t aAge[4];
  /**
     Stores bitwise copies of decks. Storing a fsl_deck_malloc() deck
     into the cache requires bitwise-copying is contents, wiping out
     its contents via assignment from fsl_deck_empty, then
     fsl_free()'ing it (as opposed to fsl_deck_finalize(), which would
     attempt to clean up memory which now belongs to the cache's
     copy).

     Array sizes of 6 and 10 do not appreciably change the hit rate
     compared to 4, at least not for current (2021-11-18) uses.
  */
  fsl_deck decks[4];
};

/** Convenience typedef. */
typedef struct fsl__mcache fsl__mcache;

/** Initialized-with-defaults fsl__mcache structure, intended for
    const-copy initialization. */
#define fsl__mcache_empty_m { \
  0,0,0,                      \
  {/*aAge*/0,0,0,0},          \
  {fsl_deck_empty_m,fsl_deck_empty_m, \
   fsl_deck_empty_m, fsl_deck_empty_m}\
}

/** Initialized-with-defaults fsl__mcache structure, intended for
    non-const copy initialization. */
extern const fsl__mcache fsl__mcache_empty;


/* The fsl_cx class is documented in main public header. */
struct fsl_cx {
  /**
     A pointer to the "main" db handle. Exactly which db IS the
     main db is, because we have three DBs, not generally knowble.

     The internal management of fsl_cx's db handles has changed
     a couple of times. As of 2022-01-01 the following applies:

     dbMain starts out NULL. When a repo or checkout is opened,
     dbMain is pointed at the first of those which is opened.
     When its parter is opened, it is ATTACHed to dbMain.
     dbMain->role holds a bitmask of fsl_dbrole_e values reflecing
     which roles are opened/attached to it.

     The db-specific separate handles (this->{repo,ckout}.db) are used
     to store the name and file path to each db. ONE of those will
     have a db->dbh value of non-NULL, and that one is the instance
     which dbMain points to.

     Whichever db is opened first gets aliased to the corresponding
     fsl_db_role_name() for is role so that SQL code need not care
     which db is "main" and which is "repo" or "ckout". (Sidebar: when
     this library was started, the ability to alias a db with another
     name did not exist, and thus we required a middle-man "main" db
     to which we ATTACHed the repo and checkout dbs.)

     As of 20211230, f->config.db is its own handle, not ATTACHed with
     the others. Its db gets aliased to
     fsl_db_role_name(FSL_DBROLE_CONFIG).

     Internal code should rely as little as possible on the actual
     arrangement of internal DB handles, and should use
     fsl_cx_db_repo(), fsl_cx_db_ckout(), and fsl_cx_db_config() to
     get a handle to the specific db they want. Whether or not they
     return the same handle or 3 different ones may change at some
     point, so the public API treats them as separate entities. That
     is especially important for the global config db, as that one
     is (for locking reason) almost certain to remain in its own
     db handle, independent of the repo/checkout dbs.

     In any case, the internals very much rely on the repo and
     checkout dbs being accessible via a single db handle because the
     checkout-related SQL requires both dbs for most queries. The
     internals are less picky about the genuine layout of those
     handles (e.g. which one, if either, is the MAIN db).
  */
  fsl_db * dbMain;

  /**
     Marker which tells us whether fsl_cx_finalize() needs
     to fsl_free() this instance or not.
  */
  void const * allocStamp;

  /**
     Holds info directly related to a checkout database.
  */
  struct {
    /**
       Holds the filename of the current checkout db and possibly
       related state.
    */
    fsl_db db;
    /**
       The directory part of an opened checkout db. This is currently
       only set by fsl_ckout_open_dir(). It contains a trailing slash,
       largely because that simplifies porting fossil(1) code and
       appending filenames to this directory name to create absolute
       paths (a frequently-needed option).

       Useful for doing absolute-to-relative path conversions for
       checking file lists.
    */
    char * dir;
    /**
       Optimization: fsl_strlen() of dir. Guaranteed to be set to
       dir's length if dir is not NULL, else it will be 0.
    */
    fsl_size_t dirLen;
    /**
       The rid of the current checkout. May be 0 for an empty
       repo/checkout. Must be negative if not yet known.
    */
    fsl_id_t rid;
    /**
       The UUID of the current checkout. Only set if this->rid is
       positive. Owned by the containing fsl_cx object.
    */
    fsl_uuid_str uuid;
    /**
       Julian mtime of the checkout version, as reported by the
       [event] table.
    */
    double mtime;
  } ckout;

  /**
     Holds info directly related to a repo database.
  */
  struct {
    /**
       Holds the filename of the current repo db and possibly related
       state.
    */
    fsl_db db;
    /**
       The default user name, for operations which need one.
       See fsl_cx_user_set().
    */
    char * user;
  } repo;

  /**
     Holds info directly related to a global config database.
  */
  struct {
    /**
       Holds the filename of the current global config db and possibly
       related state. This handle is managed separately from the
       repo/ckout handles because this db is shared by all fossil
       instances are we have to ensure that we don't lock it for
       longer than necessary, thus this db may get opened and closed
       multiple times within even within a short-lived application.
    */
    fsl_db db;
  } config;

  /**
     State for incrementally preparing a checkin operation.
  */
  struct {
    /**
       Holds a list of "selected files" in the form
       of vfile.id values.
    */
    fsl_id_bag selectedIds;

    /**
       The deck used for incrementally building certain parts of a
       checkin.
    */
    fsl_deck mf;
  } ckin;

  /**
     Confirmation callback. Used by routines which may have to
     interactively ask a user to confirm things.
  */
  fsl_confirmer confirmer;

  /**
     Output channel used by fsl_output() and friends.

     This was added primarily so that fossil client apps can share a
     single output channel which the user can swap out, e.g. to direct
     all output to a UI widget or a file.

     Though the library has adamantly avoided adding a "warning"
     output channel, features like:

     https://fossil-scm.org/home/info/52a389d3dbd4b6cc

     arguably argue for one.
  */
  fsl_outputer output;

  /**
     Can be used to tie client-specific data to the context. Its
     finalizer is called when fsl_cx_finalize() cleans up. The library
     does not use this state. It is intended primarily for tying,
     e.g., scripting-engine information to the context, e.g. mapping a
     scripting engine context to this one for later use in fossil-side
     callbacks.
  */
  fsl_state clientState;

  /**
     Holds error state. As a general rule, this information is updated
     only by routines which need to return more info than a simple
     integer error code. e.g. this is often used to hold
     db-driver-provided error state. It is not used by "simple"
     routines for which an integer code always suffices. APIs which
     set this should denote it with a comment like "updates the
     context's error state on error."
  */
  fsl_error error;

  /**
     Reuseable scratchpads for low-level file canonicalization
     buffering and whatnot. Not intended for huge content: use
     this->fileContent for that. This list should stay relatively
     short, as should the buffers (a few kb each, at most).

     @see fsl__cx_scratchpad()
     @see fsl__cx_scratchpad_yield()
  */
  struct {
    /**
       Strictly-internal temporary buffers we intend to reuse many
       times, mostly for filename canonicalization, holding hash
       values, and small encoding/decoding tasks. These must never be
       used for values which will be long-lived, nor are they intended
       to be used for large content, e.g. reading files, with the
       possible exception of holding versioned config settings, as
       those are typically rather small.

       If needed, the lengths of this->buf[] and this->used[] may be
       extended, but anything beyond 8, maybe 10, seems a bit extreme.
       They should only be increased if we find code paths which
       require it. As of this writing (2021-03-17), the peak
       concurrently used was 5. In any case fsl__cx_scratchpad() fails
       fatally if it needs more than it has, so we won't/can't fail to
       overlook such a case.
    */
    fsl_buffer buf[8];
    /**
       Flags telling us which of this->buf is currenly in use.
    */
    bool used[8];
    /**
       A cursor _hint_ to try to speed up fsl__cx_scratchpad() by about
       half a nanosecond, making it O(1) instead of O(small N) for the
       common case.
    */
    short next;
  } scratchpads;

  /**
     A bitwise copy of the config object passed to fsl_cx_init() (or
     some default).
  */
  fsl_cx_config cxConfig;

  /**
     Flags, some (or one) of which is runtime-configurable by the
     client (see fsl_cx_flags_e). We can get rid of this and add the
     flags to the cache member along with the rest of them.
  */
  int flags;

  /**
     Error flag which is intended to be set via signal handlers or a
     UI thread to tell this context to cancel any currently
     long-running operation. Not all operations honor this check, but
     the ones which are prone to "exceedingly long" operation (at
     least a few seconds) do.
  */
  volatile int interrupted;

  /**
     List of callbacks for deck crosslinking purposes.
  */
  fsl_xlinker_list xlinkers;

  /**
     A place for caching generic things.
  */
  struct {
    /**
       If true, skip "dephantomization" of phantom blobs.  This is a
       detail from fossil(1) with as-yet-undetermined utility. It's
       apparently only used during the remote-sync process, which this
       API does not (as of 2021-10) yet have.
    */
    bool ignoreDephantomizations;

    /**
       Whether or not a running commit process should be marked as
       private. This member is used for communicating this flag
       through multiple levels of API.
    */
    bool markPrivate;

    /**
       True if fsl__crosslink_begin() has been called but
       fsl__crosslink_end() is still pending.
    */
    bool isCrosslinking;

    /**
       Flag indicating that only cluster control artifacts should be
       processed by manifest crosslinking. This will only be relevant
       if/when the sync protocol is implemented.
    */
    bool xlinkClustersOnly;

    /**
       Is used to tell the content-save internals that a "final
       verification" (a.k.a. verify-before-commit) is underway.
    */
    bool inFinalVerify;

    /**
       Specifies whether SOME repository-level file-name
       comparisons/searches will work case-insensitively. <0 means
       not-yet-determined, 0 = no, >0 = yes.
    */
    short caseInsensitive;

    /**
       Cached copy of the allow-symlinks config option, because it is
       (hypothetically) needed on many stat() call. Negative
       value=="not yet determined", 0==no, positive==yes. The negative
       value means we need to check the repo config resp. the global
       config to see if this is on.

       As of late 2020, fossil(1) is much more restrictive with
       symlinks due to vulnerabilities which were discovered by a
       security researcher, and we definitely must not default any
       symlink-related features to enabled/on. As of Feb. 2021, my
       personal preference, and very likely plan of attack, is to only
       treat SCM'd symlinks as if symlinks support is disabled. It's
       very unlikely that i will implement "real" symlink support but
       would, *solely* for compatibility with fossil(1), be convinced
       to permit such changes if someone else wants to implement them.
       Patches are joyfully considered!
    */
    short allowSymlinks;

    /**
       Indicates whether or not this repo has ever seen a delta
       manifest. If none has ever been seen then the repository will
       prefer to use baseline (non-delta) manifests. Once a delta is
       seen in the repository, the checkin algorithm is free to choose
       deltas later on unless its otherwise prohibited, e.g. by the
       `forbid-delta-manifests` config db setting.

       This article provides an overview to the topic delta manifests
       and essentially debunks their ostensible benefits:

       https://fossil-scm.org/home/doc/tip/www/delta-manifests.md

       Values: negative==undetermined, 0==no, positive==yes. This is
       updated when a repository is first opened and when new content
       is written to it.
    */
    short seenDeltaManifest;

    /**
       Records whether this repository has an FTS search
       index. <0=undetermined, 0=no, >0=yes.
    */
    short searchIndexExists;

    /**
       Cache for the `manifest` config setting, as used by
       fsl_ckout_manifest_setting(), with the caveat that
       if the setting changes after it is cached, we won't necessarily
       see that here!
    */
    short manifestSetting;

    /**
       Record ID of rcvfrom entry during commits. This is likely to
       remain unused in libf until/unless the sync protocol is
       implemented.
    */
    fsl_id_t rcvId;

    /**
       A place for temporarily holding file content. We use this in
       places where we have to loop over files and read their entire
       contents, so that we can reuse this buffer's memory if
       possible.  The loop and the reading might be happening in
       different functions, though, and some care must be taken to
       avoid use in two functions concurrently.
    */
    fsl_buffer fileContent;

    /**
       Reusable buffer for creating and fetching deltas via
       fsl_content_get() and fsl__content_deltify(). The number of
       allocations this actually saves is pretty small.
    */
    fsl_buffer deltaContent;
    
    /**
       fsl_content_get() cache.
    */
    fsl__bccache blobContent;
    /**
       Used during manifest parsing to keep track of artifacts we have
       seen. Whether that's really necessary or is now an unnecessary
       porting artifact (haha) is unclear.
    */
    fsl_id_bag mfSeen;
    /**
       Used during the processing of manifests to keep track of
       "leaf checks" which need to be done downstream.
    */
    fsl_id_bag leafCheck;
    /**
       Holds the RID of every record awaiting verification
       during the verify-at-commit checks.
    */
    fsl_id_bag toVerify;
    /**
       Infrastructure for fsl_mtime_of_manifest_file(). It
       remembers the previous RID so that it knows when it has to
       invalidate/rebuild its ancestry cache.
    */
    fsl_id_t mtimeManifest;
    /**
       The "project-code" config option. We do not currently (2022-01)
       use this but it will be important if/when the sync protocol is
       implemented or we want to create hashes, e.g. for user
       passwords, which depend in part on the project code.
    */
    char * projectCode;

    /**
       Internal optimization to avoid duplicate fsl_stat() calls
       across two functions in some cases.
    */
    fsl_fstat fstat;

    /**
       Parsed-deck cache.
    */
    fsl__mcache mcache;
    
    /**
       Holds various glob lists. That said... these features are
       actually app-level stuff which the library itself does not
       resp. should not enforce. We can keep track of these for users
       but the library internals _generally_ have no business using
       them.

       _THAT_ said... these have enough generic utility that we can
       justify storing them and _optionally_ applying them. See
       fsl_checkin_opt for an example of where we do this.
    */
    struct {
      /**
         Holds the "ignore-glob" globs.
      */
      fsl_list ignore;
      /**
         Holds the "binary-glob" globs.
      */
      fsl_list binary;
      /**
         Holds the "crnl-glob" globs.
      */
      fsl_list crnl;
    } globs;

    /**
       Very-frequently-used SQL statements. This are not stored as
       pointers into the fsl_db_prepare_cached() list because that
       cache can be invalidated via a db-close. These particular
       statements are potentially prepared so often that we manage
       them separate from fsl_db_prepare_cached() as a further
       optimization.

       This optimization was single-handedly responsible for cutting
       f-rebuild's time in less than half. The rest of that time was
       spent building the SQL strings (indirectly via fsl_appendf())
       to figure out if they corresponded to a cached query or not.
    */
    struct {
      fsl_stmt deltaSrcId;
      fsl_stmt uuidToRid;
      fsl_stmt uuidToRidGlob;
      fsl_stmt contentSize;
      fsl_stmt nextEntry;
    } stmt;

    /**
       Holds a list of temp-dir names. Must be allocated using
       fsl_temp_dirs_get() and freed using fsl_temp_dirs_free().
    */
    char **tempDirs;
  } cache;

  /**
     Ticket-related information.
  */
  struct {
    /**
       Holds a list of (fsl_card_J*) records representing custom
       ticket table fields available in the db.

       Each entry's flags member denote (using fsl_card_J_flags)
       whether that field is used by the ticket or ticketchng
       tables.

       TODO, eventually: add a separate type for these entries.  We
       use fsl_card_J because the infrastructure is there and they
       provide what we need, but fsl_card_J::flags only exists for
       this list. A custom type would be smaller than fsl_card_J
       (only two members) but adding it requires adding some
       infrastructure which isn't worth the effort at the moment.
    */
    fsl_list customFields;

    /**
       Gets set to true (at some point) if the client has the
       ticket db table.
    */
    bool hasTicket;
    /**
       Gets set to true (at some point) if the client has the
       ticket.tkt_ctime db field.
    */
    bool hasCTime;

    /**
       Gets set to true (at some point) if the client has the
       ticketchng db table.
    */
    bool hasChng;
    /**
       Gets set to true (at some point) if the client has the
       ticketchng.rid db field.
    */
    bool hasChngRid;

    /**
       The name of the ticket-table field which refers to a ticket's
       title. Default = "title". The bytes are owned by this object.
    */
    char * titleColumn;
    /**
       The name of the ticket-table field which refers to a ticket's
       status. Default = "status". The bytes are owned by this object.
    */
    char * statusColumn;
  } ticket;

  /*
    Note: no state related to server/user/etc. That is higher-level
    stuff. We might need to allow the user to set a default user
    name to avoid that he has to explicitly set it on all of the
    various Control Artifact-generation bits which need it.
  */
};

/** @internal

    Initialized-with-defaults fsl_cx struct.
*/
#define fsl_cx_empty_m {                                \
    NULL /*dbMain*/,                                    \
    NULL/*allocStamp*/,                               \
    {/*ckout*/                                        \
      fsl_db_empty_m /*db*/,                          \
      NULL /*dir*/, 0/*dirLen*/,                    \
      -1/*rid*/, NULL/*uuid*/, 0/*mtime*/        \
    },                                            \
    {/*repo*/ fsl_db_empty_m /*db*/,                  \
       0/*user*/                                     \
    },                                            \
    {/*config*/ fsl_db_empty_m /*db*/ },              \
    {/*ckin*/                                         \
      fsl_id_bag_empty_m/*selectedIds*/,            \
      fsl_deck_empty_m/*mf*/                     \
    },                                            \
    fsl_confirmer_empty_m/*confirmer*/,           \
    fsl_outputer_FILE_m /*output*/,                 \
    fsl_state_empty_m /*clientState*/,            \
    fsl_error_empty_m /*error*/,                  \
    {/*scratchpads*/ \
      {fsl_buffer_empty_m,fsl_buffer_empty_m,     \
      fsl_buffer_empty_m,fsl_buffer_empty_m,      \
      fsl_buffer_empty_m,fsl_buffer_empty_m},     \
      {false,false,false,false,false,false},      \
      0/*next*/                                   \
    },                                            \
    fsl_cx_config_empty_m /*cxConfig*/,           \
    FSL_CX_F_DEFAULTS/*flags*/,                   \
    0/*interrupted*/,                             \
    fsl_xlinker_list_empty_m/*xlinkers*/,         \
    {/*cache*/                                    \
      false/*ignoreDephantomizations*/,          \
      false/*markPrivate*/,                         \
      false/*isCrosslinking*/,                      \
      false/*xlinkClustersOnly*/,                   \
      false/*inFinalVerify*/,                       \
      -1/*caseInsensitive*/,                   \
      -1/*allowSymlinks*/,                      \
      -1/*seenDeltaManifest*/,                  \
      -1/*searchIndexExists*/,                  \
      -1/*manifestSetting*/,\
      0/*rcvId*/,                               \
      fsl_buffer_empty_m /*fileContent*/,         \
      fsl_buffer_empty_m /*deltaContent*/,        \
      fsl__bccache_empty_m/*blobContent*/,               \
      fsl_id_bag_empty_m/*mfSeen*/,           \
      fsl_id_bag_empty_m/*leafCheck*/,        \
      fsl_id_bag_empty_m/*toVerify*/,         \
      0/*mtimeManifest*/,                     \
      NULL/*projectCode*/,                    \
      fsl_fstat_empty_m/*fstat*/,             \
      fsl__mcache_empty_m/*mcache*/,           \
      {/*globs*/                              \
        fsl_list_empty_m/*ignore*/,           \
        fsl_list_empty_m/*binary*/,         \
        fsl_list_empty_m/*crnl*/            \
      },                                \
      {/*stmt*/                       \
        fsl_stmt_empty_m/*deltaSrcId*/,   \
        fsl_stmt_empty_m/*uuidToRid*/,        \
        fsl_stmt_empty_m/*uuidToRidGlob*/,        \
        fsl_stmt_empty_m/*contentSize*/,        \
        fsl_stmt_empty_m/*???*/ \
      },                                    \
      NULL/*tempDirs*/ \
    }/*cache*/,                         \
    {/*ticket*/                             \
      fsl_list_empty_m/*customFields*/,     \
      0/*hasTicket*/,                       \
      0/*hasCTime*/,                        \
      0/*hasChng*/,                         \
      0/*hasCngRid*/,                     \
      NULL/*titleColumn*/,                      \
      NULL/*statusColumn*/                    \
    }                                       \
  }

/** @internal
    Initialized-with-defaults fsl_cx instance.
*/
extern const fsl_cx fsl_cx_empty;

/*
  TODO:

  int fsl_buffer_append_getenv( fsl_buffer * b, char const * env )

  Fetches the given env var and appends it to b. Returns FSL_RC_NOT_FOUND
  if the env var is not set. The primary use for this would be to simplify
  the Windows implementation of fsl_find_home_dir().
*/


/** @internal

    Expires the single oldest entry in c. Returns true if it removes
    an item, else false.
*/
bool fsl__bccache_expire_oldest(fsl__bccache * const c);

/** @internal

    Add an entry to the content cache.

    This routines transfers the contents of pBlob over to c,
    regardless of success or failure.  The cache will deallocate the
    memory when it has finished with it.

    If the cache cannot add the entry due to cache-internal
    constraints, as opposed to allocation errors, it clears the buffer
    (for consistency's sake) and returns 0.

    Returns 0 on success, FSL_RC_OOM on allocation error. Has undefined
    behaviour if !c, rid is not semantically valid, !pBlob. An empty
    blob is normally semantically illegal but is not strictly illegal
    for this cache's purposes.
*/
int fsl__bccache_insert(fsl__bccache * const c, fsl_id_t rid,
                       fsl_buffer * const pBlob);

/** @internal

    Frees all memory held by c, and clears out c's state, but does
    not free c. Results are undefined if !c.
*/
void fsl__bccache_clear(fsl__bccache * const c);

/** @internal

    Resets all bags associated with the given cache and frees all
    cached buffer memory, but keeps any fsl_id_bag memory intact for
    re-use. This does not reset the hit/miss metrics.
*/
void fsl__bccache_reset(fsl__bccache * const c);


/** @internal

    Checks f->cache.blobContent to see if rid is available in the
    repository opened by f.

    Returns 0 if the content for the given rid is available in the
    repo or the cache. Returns FSL_RC_NOT_FOUND if it is not in the
    repo nor the cache. Returns some other non-0 code for "real
    errors," e.g. FSL_RC_OOM if a cache allocation fails. This
    operation may update the cache's contents.

    If this function detects a loop in artifact lineage, it fails an
    assert() in debug builds and returns FSL_RC_CONSISTENCY in
    non-debug builds. That doesn't happen in real life, though.
*/
int fsl__bccache_check_available(fsl_cx * const f, fsl_id_t rid);

/** @internal

    This is THE ONLY routine which adds content to the blob table.

    This writes the given buffer content into the repository
    database's blob tabe. It Returns the record ID via outRid (if it
    is not NULL).  If the content is already in the database (as
    determined by a lookup of its hash against blob.uuid), this
    routine fetches the RID (via *outRid) but has no side effects in
    the repo.

    If srcId is >0 then pBlob must contain delta content from
    the srcId record. srcId might be a phantom.  

    pBlob is normally uncompressed text, but if uncompSize>0 then
    the pBlob value is assumed to be compressed (via fsl_buffer_compress()
    or equivalent) and uncompSize is
    its uncompressed size. If uncompSize>0 then zUuid must be valid
    and refer to the hash of the _uncompressed_ data (which is why
    this routine does not calculate it for the client).

    Sidebar: we "could" use fsl_buffer_is_compressed() and friends
    to determine if pBlob is compressed and get its decompressed
    size, then remove the uncompSize parameter, but that would
    require that this function decompress the content to calculate
    the hash. Since the caller likely just compressed it, that seems
    like a huge waste.

    zUuid is the UUID of the artifact, if it is not NULL.  When
    srcId is specified then zUuid must always be specified.  If
    srcId is zero, and zUuid is zero then the correct zUuid is
    computed from pBlob.  If zUuid is not NULL then this function
    asserts (in debug builds) that fsl_is_uuid() returns true for
    zUuid.

    If isPrivate is true, the blob is created as a private record.

    If the record already exists but is a phantom, the pBlob content
    is inserted and the phatom becomes a real record.

    The original content of pBlob is not disturbed.  The caller continues
    to be responsible for pBlob.  This routine does *not* take over
    responsibility for freeing pBlob.

    If outRid is not NULL then on success *outRid is assigned to the
    RID of the underlying blob record.

    Returns 0 on success and there are too many potential error cases
    to name - this function is a massive beast.

    Potential TODO: we don't really need the uncompSize param - we
    can deduce it, if needed, based on pBlob's content. We cannot,
    however, know the UUID of the decompressed content unless the
    client passes it in to us.

    @see fsl__content_put()
*/
int fsl__content_put_ex( fsl_cx * const f,
                        fsl_buffer const * pBlob,
                        fsl_uuid_cstr zUuid, fsl_id_t srcId,
                        fsl_size_t uncompSize, bool isPrivate,
                        fsl_id_t * outRid);
/** @internal

    Equivalent to fsl__content_put_ex(f,pBlob,NULL,0,0,0,newRid).

    This must only be used for saving raw (non-delta) content.

    @see fsl__content_put_ex()
*/
int fsl__content_put( fsl_cx * const f,
                     fsl_buffer const * pBlob,
                     fsl_id_t * newRid);


/** @internal

    If the given blob ID refers to deltified repo content, this routine
    undeltifies it and replaces its content with its expanded
    form.

    Returns 0 on success, FSL_RC_MISUSE if !f, FSL_RC_NOT_A_REPO if
    f has no opened repository, FSL_RC_RANGE if rid is not positive,
    and any number of other potential errors during the db and
    content operations. This function treats already unexpanded
    content as success.

    @see fsl__content_deltify()
*/
int fsl__content_undeltify(fsl_cx * const f, fsl_id_t rid);


/** @internal

    The converse of fsl__content_undeltify(), this replaces the storage
    of the given blob record so that it is a delta of srcid.

    If rid is already a delta from some other place then no
    conversion occurs and this is a no-op unless force is true.

    If rid's contents are not available because the the rid is a
    phantom or depends to one, no delta is generated and 0 is
    returned.

    It never generates a delta that carries a private artifact into
    a public artifact. Otherwise, when we go to send the public
    artifact on a sync operation, the other end of the sync will
    never be able to receive the source of the delta.  It is OK to
    delta private->private, public->private, and public->public.
    Just no private->public delta. For such cases this function
    returns 0, as opposed to FSL_RC_ACCESS or some similar code, and
    leaves the content untouched.

    If srcid is a delta that depends on rid, then srcid is
    converted to undelta'd text.

    If either rid or srcid contain less than some "small,
    unspecified number" of bytes (currently 50), or if the resulting
    delta does not achieve a compression of at least 25%, the rid is
    left untouched.

    Returns 0 if a delta is successfully made or none needs to be
    made, non-0 on error.

    @see fsl__content_undeltify()
*/
int fsl__content_deltify(fsl_cx * const f, fsl_id_t rid,
                        fsl_id_t srcid, bool force);


/** @internal

    Creates a new phantom blob with the given UUID and return its
    artifact ID via *newId. Returns 0 on success, FSL_RC_MISUSE if
    !f or !uuid, FSL_RC_RANGE if fsl_is_uuid(uuid) returns false,
    FSL_RC_NOT_A_REPO if f has no repository opened, FSL_RC_ACCESS
    if the given uuid has been shunned, and about 20 other potential
    error codes from the underlying db calls. If isPrivate is true
    _or_ f has been flagged as being in "private mode" then the new
    content is flagged as private. newId may be NULL, but if it is
    then the caller will have to find the record id himself by using
    the UUID (see fsl_uuid_to_rid()).
*/
int fsl__content_new( fsl_cx * const f, fsl_uuid_cstr uuid,
                      bool isPrivate, fsl_id_t * const newId );

/** @internal

    Check to see if checkin "rid" is a leaf and either add it to the LEAF
    table if it is, or remove it if it is not.

    Returns 0 on success, FSL_RC_MISUSE if !f or f has no repo db
    opened, FSL_RC_RANGE if pid is <=0. Other errors
    (e.g. FSL_RC_DB) may indicate that db is not a repo. On error
    db's error state may be updated.
*/
int fsl__repo_leafcheck(fsl_cx * const f, fsl_id_t pid);  

/** @internal

    Schedules a leaf check for "rid" and its parents. Returns 0 on
    success.
*/
int fsl__repo_leafeventually_check( fsl_cx * const f, fsl_id_t rid);

/** @internal

    Perform all pending leaf checks. Returns 0 on success or if it
    has nothing to do.
*/
int fsl__repo_leafdo_pending_checks(fsl_cx * const f);

/** @internal

    Inserts a tag into f's repo db. It does not create the related
    control artifact - use fsl_tag_an_rid() for that.

    rid is the artifact to which the tag is being applied.

    srcId is the artifact that contains the tag. It is often, but
    not always, the same as rid. This is often the RID of the
    manifest containing tags added as part of the commit, in which
    case rid==srcId. A Control Artifact which tags a different
    artifact will have rid!=srcId.

    mtime is the Julian timestamp for the tag. Defaults to the
    current time if mtime <= 0.0.

    If outRid is not NULL then on success *outRid is assigned the
    record ID of the generated tag (the tag.tagid db field).

    If a more recent (compared to mtime) entry already exists for
    this tag/rid combination then its tag.tagid is returned via
    *outRid (if outRid is not NULL) and no new entry is created.

    Returns 0 on success, and has a huge number of potential error
    codes.
*/
int fsl__tag_insert( fsl_cx * const f,
                     fsl_tagtype_e tagtype,
                     char const * zTag,
                     char const * zValue,
                     fsl_id_t srcId,
                     double mtime,
                     fsl_id_t rid,
                     fsl_id_t *outRid );
/** @internal

    Propagate all propagatable tags in artifact pid to the children of
    pid. Returns 0 on... non-error. Returns FSL_RC_RANGE if pid<=0.
*/
int fsl__tag_propagate_all(fsl_cx * const f, fsl_id_t pid);

/** @internal

    Propagates a tag through the various internal pipelines.

    pid is the artifact id to whose children the tag should be
    propagated.

    tagid is the id of the tag to propagate (the tag.tagid db value).

    tagType is the type of tag to propagate. Must be either
    FSL_TAGTYPE_CANCEL or FSL_TAGTYPE_PROPAGATING. Note that
    FSL_TAGTYPE_ADD is not permitted.  The tag-handling internals
    (other than this function) translate ADD to CANCEL for propagation
    purposes. A CANCEL tag is used to stop propagation. (That's a
    historical behaviour inherited from fossil(1).) A potential TODO
    is for this function to simply treat ADD as CANCEL, without
    requiring that the caller be sure to never pass an ADD tag.

    origId is the artifact id of the origin tag if tagType ==
    FSL_TAGTYPE_PROPAGATING, otherwise it is ignored.

    zValue is the optional value for the tag. May be NULL.

    mtime is the Julian timestamp for the tag. Must be a valid time
    (no defaults here).

    This function is unforgiving of invalid values/ranges, and may assert
    in debug mode if passed invalid ids (values<=0), a NULL f, or if f has
    no opened repo.
*/
int fsl__tag_propagate(fsl_cx * const f,
                      fsl_tagtype_e tagType,
                      fsl_id_t pid,
                      fsl_id_t tagid,
                      fsl_id_t origId,
                      const char *zValue,
                      double mtime );

/** @internal

    Clears the "seen" cache used by manifest parsing. Should be
    called by routines which initialize parsing, but not until their
    work has finished all parsing (so that recursive parsing can
    use that cache).

    If freeMemory is true the cache's list memory is freed, otherwise
    the cache is reset for reuse without clearing its memory.
*/
void fsl__cx_clear_mf_seen(fsl_cx * const f, bool freeMemory);

/** @internal

    Generates an fsl_appendf()-formatted message to stderr and
    fatally aborts the application by calling exit(). This is only
    (ONLY!) intended for us as a placeholder for certain test cases
    and is neither thread-safe nor reantrant.

    fmt may be empty or NULL, in which case only the code and its
    fsl_rc_cstr() representation are output.

    This function does not return.
*/
void fsl__fatal( int code, char const * fmt, ... )
#ifdef __GNUC__
  __attribute__ ((noreturn))
#endif
  ;

/** @internal

    Translate a normalized, repo-relative filename into a
    filename-id (fnid). Create a new fnid if none previously exists
    and createNew is true. On success returns 0 and sets *rv to the
    filename.fnid record value. If createNew is false and no match
    is found, 0 is returned but *rv will be set to 0. Returns non-0
    on error.  Results are undefined if any parameter is NULL.


    In debug builds, this function asserts that no pointer arguments
    are NULL and that f has an opened repository.
*/
int fsl__repo_filename_fnid2( fsl_cx * f, char const * filename,
                             fsl_id_t * rv, bool createNew );


/** @internal

    Clears and frees all (char*) members of db but leaves the rest
    intact. If alsoErrorState is true then the error state is also
    freed, else it is kept as well.
*/
void fsl__db_clear_strings(fsl_db * const db, bool alsoErrorState );

/** @internal

    Returns 0 if db appears to have a current repository schema, 1
    if it appears to have an out of date schema, and -1 if it
    appears to not be a repository. Results are undefined if db is
    NULL or not opened.
*/
int fsl__db_repo_verify_schema(fsl_db * db);


/** @internal

    Flags for APIs which add phantom blobs to the repository.  The
    values in this enum derive from fossil(1) code and should not be
    changed without careful forethought and (afterwards) testing.  A
    phantom blob is a blob about whose existence we know but for which
    we have no content. This normally happens during sync or rebuild
    operations, but can also happen when artifacts are stored directly
    as files in a repo (like this project's repository does, storing
    artifacts from *other* projects for testing purposes).
*/
enum fsl__phantom_e {
/**
   Indicates to fsl__uuid_to_rid2() that no phantom artifact
   should be created.
*/
FSL_PHANTOM_NONE = 0,
/**
   Indicates to fsl__uuid_to_rid2() that a public phantom
   artifact should be created if no artifact is found.
*/
FSL_PHANTOM_PUBLIC = 1,
/**
   Indicates to fsl__uuid_to_rid2() that a private phantom
   artifact should be created if no artifact is found.
*/
FSL_PHANTOM_PRIVATE = 2
};
typedef enum fsl__phantom_e fsl__phantom_e;

/** @internal

    Works like fsl_uuid_to_rid(), with these differences:

    - uuid is required to be a complete UUID, not a prefix.

    - If it finds no entry and the mode argument specifies so then
    it will add either a public or private phantom entry and return
    its new rid. If mode is FSL_PHANTOM_NONE then this this behaves
    just like fsl_uuid_to_rid().

    Returns a positive value on success, 0 if it finds no entry and
    mode==FSL_PHANTOM_NONE, and a negative value on error (e.g. if
    fsl_is_uuid(uuid) returns false). Errors which happen after
    argument validation will "most likely" update f's error state
    with details.
*/
fsl_id_t fsl__uuid_to_rid2( fsl_cx * const f, fsl_uuid_cstr uuid,
                           fsl__phantom_e mode );

/** @internal

    Schedules the given rid to be verified at the next commit. This
    is used by routines which add artifact records to the blob
    table.

    The only error case, assuming the arguments are valid, is an
    allocation error while appending rid to the internal to-verify
    queue.

    @see fsl__repo_verify_at_commit()
    @see fsl_repo_verify_cancel()
*/
int fsl__repo_verify_before_commit( fsl_cx * const f, fsl_id_t rid );

/** @internal

    Clears f's verify-at-commit list of RIDs.

    @see fsl__repo_verify_at_commit()
    @see fsl__repo_verify_before_commit()
*/
void fsl_repo_verify_cancel( fsl_cx * const f );

/** @internal

    Processes all pending verify-at-commit entries and clears the
    to-verify list. Returns 0 on success. On error f's error state
    will likely be updated.

    ONLY call this from fsl_db_transaction_end() or its delegate (if
    refactored).

    Verification calls fsl_content_get() to "unpack" content added in
    the current transaction. If fetching the content (which applies
    any deltas it may need to) fails or a checksum does not match then
    this routine fails and returns non-0. On error f's error state
    will be updated.

    @see fsl_repo_verify_cancel()
    @see fsl__repo_verify_before_commit()
*/
int fsl__repo_verify_at_commit( fsl_cx * const f );

/** @internal

    Removes all entries from the repo's blob table which are listed
    in the shun table. Returns 0 on success. This operation is
    wrapped in a transaction. Delta contant which depend on
    to-be-shunned content are replaced with their undeltad forms.

    Returns 0 on success.
*/
int fsl__repo_shun_artifacts(fsl_cx * const f);

/** @internal.

    Return a pointer to a string that contains the RHS of an SQL IN
    operator which will select config.name values that are part of
    the configuration that matches iMatch (a bitmask of
    fsl_configset_e values). Ownership of the returned string is
    passed to the caller, who must eventually pass it to
    fsl_free(). Returns NULL on allocation error.

    Reminder to self: this is part of the infrastructure for copying
    config state from an existing repo when creating new repo.
*/
char *fsl__config_inop_rhs(int iMask);

/** @internal

    Return a pointer to a string that contains the RHS of an IN
    operator that will select config.name values that are in the
    list of control settings. Ownership of the returned string is
    passed to the caller, who must eventually pass it to
    fsl_free(). Returns NULL on allocation error.

    Reminder to self: this is part of the infrastructure for copying
    config state from an existing repo when creating new repo.
*/
char *fsl_db_setting_inop_rhs();

/** @internal

    Creates the ticket and ticketchng tables in f's repository db,
    DROPPING them if they already exist. The schema comes from
    fsl_schema_ticket().

    TODO? Add a flag specifying whether to drop or keep existing
    copies.

    Returns 0 on success.
*/
int fsl__cx_ticket_create_table(fsl_cx * const f);

/** @internal

    Frees all J-card entries in the given list.

    li is assumed to be empty or contain (fsl_card_J*)
    instances. If alsoListMem is true then any memory owned
    by li is also freed. li itself is not freed.

    Results are undefined if li is NULL.
*/
void fsl__card_J_list_free( fsl_list * li, bool alsoListMem );

/** @internal

    Values for fsl_card_J::flags.
*/
enum fsl_card_J_flags {
/**
   Sentinel value.
*/
FSL_CARD_J_INVALID = 0,
/**
   Indicates that the field is used by the ticket table.
*/
FSL_CARD_J_TICKET = 0x01,
/**
   Indicates that the field is used by the ticketchng table.
*/
FSL_CARD_J_CHNG = 0x02,
/**
   Indicates that the field is used by both the ticket and
   ticketchng tables.
*/
FSL_CARD_J_BOTH = FSL_CARD_J_TICKET | FSL_CARD_J_CHNG
};

/** @internal

    Loads all custom/customizable ticket fields from f's repo's
    ticket table info f. If f has already loaded the list and
    forceReload is false, this is a no-op.

    Returns 0 on success.

    @see fsl_cx::ticket::customFields
*/
int fsl__cx_ticket_load_fields(fsl_cx * const f, bool forceReload);

/** @internal

    A comparison routine for qsort(3) which compares fsl_card_J
    instances in a lexical manner based on their names. The order is
    important for card ordering in generated manifests.

    This routine expects to get passed (fsl_card_J**) (namely from
    fsl_list entries), and will not work on an array of J-cards.
*/
int fsl__qsort_cmp_J_cards( void const * lhs, void const * rhs );

/** @internal

    This function updates the repo and/or global config databases
    with links between the dbs intended for various fossil-level
    bookkeeping and housecleaning. These links are not essential to
    fossil's functionality but assist in certain "global"
    operations.

    If no checkout is opened but a repo is, the global config (if
    opened) is updated to know about the opened repo db.

    If a checkout is opened, global config (if opened) and the
    repo are updated to point to the checked-out db.
*/
int fsl__repo_record_filename(fsl_cx * const f);

/** @internal

    Updates f->ckout.uuid and f->ckout.rid to reflect the current
    checkout state. If no checkout is opened, the uuid is freed/NULLed
    and the rid is set to 0. Returns 0 on success. If it returns an
    error (OOM or db-related), the f->ckout state is left in a
    potentially inconsistent state, and it should not be relied upon
    until/unless the error is resolved.

    This is done when a checkout db is opened, when performing a
    checkin, and otherwise as needed, and so calling it from other
    code is normally not necessary.

    @see fsl__ckout_version_write()
*/
int fsl__ckout_version_fetch( fsl_cx * const f );

/** @internal

    Updates f->ckout's state to reflect the given version info and
    writes the 'checkout' and 'checkout-hash' properties to the
    currently-opened checkout db. Returns 0 on success,
    FSL_RC_NOT_A_CKOUT if no checkout is opened (may assert() in that
    case!), or some other code if writing to the db fails.

    If vid is 0 then the version info is null'd out. Else if uuid is
    NULL then fsl_rid_to_uuid() is used to fetch the UUID for vid.

    If the RID differs from f->ckout.rid then f->ckout's version state
    is updated to the new values.

    This routine also updates or removes the checkout's manifest
    files, as per fsl_ckout_manifest_write(). If vid is 0 then it
    removes any such files which themselves are not part of the
    current checkout.

    @see fsl__ckout_version_fetch()
    @see fsl_cx_ckout_version_set()
*/
int fsl__ckout_version_write( fsl_cx * const f, fsl_id_t vid,
                             fsl_uuid_cstr uuid );

/**
   @internal
   
   Exports the file with the given [vfile].[id] to the checkout,
   overwriting (if possible) anything which gets in its way. If
   the file is determined to have not been modified, it is
   unchanged.

   If the final argument is not NULL then it is set to 0 if the file
   was not modified, 1 if only its permissions were modified, and 2 if
   its contents were updated (which also requires resetting its
   permissions to match their repo-side state).

   Returns 0 on success, any number of potential non-0 codes on
   error, including, but not limited to:

   - FSL_RC_NOT_A_CKOUT - no opened checkout.
   - FSL_RC_NOT_FOUND - no matching vfile entry.
   - FSL_RC_OOM - we cannot escape this eventuality.

   Trivia:

   - fossil(1)'s vfile_to_disk() is how it exports a whole vfile, or a
   single vfile entry, to disk. e.g. it performs a checkout that way,
   whereas we currently perform a checkout using the "repo extraction"
   API. The checkout mechanism was probably the first major core
   fossil feature which was structured radically differently in
   libfossil, compared to the feature's fossil counterpart, when it
   was ported over.

   - This routine always writes to the vfile.pathname entry, as
   opposed to vfile.origname.

   Maintenance reminders: internally this code supports handling
   multiple files at once, but (A) that's not part of the interface
   and may change and (B) the 3rd parameter makes little sense in that
   case unless maybe we change it to a callback, which seems like
   overkill for our use cases.
*/
int fsl__vfile_to_ckout(fsl_cx * const f, fsl_id_t vfileId,
                       int * wasWritten);

/** @internal

    On Windows platforms (only), if fsl_isalpha(*zFile)
    and ':' == zFile[1] then this returns zFile+2,
    otherwise it returns zFile.
*/
char * fsl__file_without_drive_letter(char * zFile);

/** @internal

    This is identical to the public-API member fsl_deck_F_search(),
    except that it returns a non-const F-card.

    Locate a file named zName in d->F.list.  Return a pointer to the
    appropriate fsl_card_F object. Return NULL if not found.
   
    If d->f is set (as it is when loading decks via
    fsl_deck_load_rid() and friends), this routine works even if p is
    a delta-manifest. The pointer returned might be to the baseline
    and d->B.baseline is loaded on demand if needed.
   
    If the returned card's uuid member is NULL, it means that the file
    was removed in the checkin represented by d.

    If !d, zName is NULL or empty, or FSL_SATYPE_CHECKIN!=d->type, it
    asserts in debug builds and returns NULL in non-debug builds.

    We assume that filenames are in sorted order and use a binary
    search. As an optimization, to support the most common use case,
    searches through a deck update d->F.cursor to the last position a
    search was found. Because searches are normally done in lexical
    order (because of architectural reasons), this is normally an O(1)
    operation. It degrades to O(N) if out-of-lexical-order searches
    are performed.
*/
fsl_card_F * fsl__deck_F_seek(fsl_deck * const d, const char *zName);

/** @internal

    Ensures that f's single file content buffer is available for use
    and returns it to the caller. If it appears to already be in use,
    this function fails fatally via fsl__fatal(), indicating a serious
    misuse of the internal API.

    Calling this obligates the caller to call
    fsl__cx_content_buffer_yield() as soon as they are done with the
    buffer.
*/
fsl_buffer * fsl__cx_content_buffer(fsl_cx * const f);

/** @internal

    Part of the fsl_cx::cache::fileContent optimization. This sets
    f->cache.fileContent.used to 0 and if its capacity is over a certain
    (unspecified, unconfigurable) size then it is trimmed to that
    size.
*/
void fsl__cx_content_buffer_yield(fsl_cx * const f);

/** @internal

   Currently disabled (always returns 0) pending resolution of a
   "wha???" result from one of the underlying queries.

   Queues up the given artifact for a search index update. This is
   only intended to be called from crosslinking steps and similar
   content updates. Returns 0 on success.

   The final argument is intended only for wiki titles (the L-card of
   a wiki post).

   If the repository database has no search index or the given content
   is marked as private, this function returns 0 and makes no changes
   to the db.
*/
int fsl__search_doc_touch(fsl_cx * const f, fsl_satype_e saType,
                         fsl_id_t rid, const char * docName);

/** @internal

   Returns true if the given file name is a reserved filename
   (case-insensitive) on Windows platforms, else returns false.

   zPath must be a canonical path with forward-slash directory
   separators. nameLen is the length of zPath. If negative, fsl_strlen()
   is used to determine its length.
*/
bool fsl__is_reserved_fn_windows(const char *zPath, fsl_int_t nameLen);

/** @internal

   Clears any pending merge state from the f's checkout db's vmerge
   table. Returns 0 on success, non-0 on db error.

   If fullWipe is true, it clears all vfile contents uncondtionally,
   else it clears only entries for which the corresponding vfile
   entries are marked as unchanged and then cleans up remaining merge
   state if no file-level merge changes are pending.
*/
int fsl__ckout_clear_merge_state( fsl_cx * const f, bool fullWipe );

/** @internal

   Installs or reinstalls the checkout database schema into f's open
   checkout db. Returns 0 on success, FSL_RC_NOT_A_CKOUT if f has
   no opened checkout, or an code if a lower-level operation fails.

   If dropIfExists is true then all affected tables are dropped
   beforehand if they exist. "It's the only way to be sure."

   If dropIfExists is false and the schema appears to already exists
   (without actually validating its validity), 0 is returned.
*/
int fsl__ckout_install_schema(fsl_cx * const f, bool dropIfExists);

/** @internal

   Attempts to remove empty directories from under a checkout,
   starting with tgtDir and working upwards until it either cannot
   remove one or it reaches the top of the checkout dir.

   The second argument must be the canonicalized absolute path to some
   directory under the checkout root. The contents of the buffer may,
   for efficiency's sake, be modified by this routine as it traverses
   the directory tree. It will never grow the buffer but may mutate
   its memory's contents.

   Returns the number of directories it is able to remove.

   Results are undefined if tgtDir is not an absolute path rooted in
   f's current checkout.

   There are any number of valid reasons removal of a directory might
   fail, and this routine stops at the first one which does.
*/
unsigned int fsl__ckout_rm_empty_dirs(fsl_cx * const f,
                                      fsl_buffer const * const tgtDir);

/** @internal

   This is intended to be passed the name of a file which was just
   deleted and "might" have left behind an empty directory. The name
   _must_ an absolute path based in f's current checkout. This routine
   uses fsl_file_dirpart() to strip path components from the string
   and remove directories until either removing one fails or the top
   of the checkout is reached. Since removal of a directory can fail for
   any given reason, this routine ignores such errors. It returns 0 on
   success, FSL_RC_OOM if allocation of the working buffer for the
   filename hackery fails, and FSL_RC_MISUSE if zFilename is not
   rooted in the checkout (in which case it may assert(), so don't do
   that).

   @see fsl_is_rooted_in_ckout()
   @see fsl_rm_empty_dirs()
*/
int fsl__ckout_rm_empty_dirs_for_file(fsl_cx * const f, char const *zAbsPath);

/** @internal

    If f->cache.seenDeltaManifest<=0 then this routine sets it to 1
    and sets the 'seen-delta-manifest' repository config setting to 1,
    else this has no side effects. Returns 0 on success, non-0 if
    there is an error while writing to the repository config.
*/
int fsl__cx_update_seen_delta_deck(fsl_cx * const f);

/** @internal

   Very, VERY internal.

   Returns the next available buffer from f->scratchpads. Fatally
   aborts if there are no free buffers because "that should not
   happen."  Calling this obligates the caller to eventually pass
   its result to fsl__cx_scratchpad_yield().

   This function guarantees the returned buffer's 'used' member will be
   set to 0.

   Maintenance note: the number of buffers is hard-coded in the
   fsl_cx::scratchpads anonymous struct.
*/
fsl_buffer * fsl__cx_scratchpad(fsl_cx * const f);

/** @internal

   Very, VERY internal.

   "Yields" a buffer which was returned from fsl__cx_scratchpad(),
   making it available for re-use. The caller must treat the buffer as
   if this routine frees it: using the buffer after having passed it
   to this function will internally be flagged as explicit misuse and
   will lead to a fatal crash the next time that buffer is fetched via
   fsl__cx_scratchpad(). So don't do that.
*/
void fsl__cx_scratchpad_yield(fsl_cx * const f, fsl_buffer * const b);

/** @internal

   Run automatically by fsl_deck_save(), so it needn't normally be run
   aside from that, at least not from average client code.

   Runs postprocessing on the Structural Artifact represented by
   d. d->f must be set, d->rid must be set and valid and d's contents
   must accurately represent the stored manifest for the given
   rid. This is normally run just after the insertion of a new
   manifest, but is sometimes also run after reading a deck from the
   database (in order to rebuild all db relations and add/update the
   timeline entry).

   Returns 0 on succes, FSL_RC_MISUSE !d->f, FSL_RC_RANGE if
   d->rid<=0, FSL_RC_MISUSE (with more error info in f) if d does not
   contain all required cards for its d->type value. It may return
   various other codes from the many routines it delegates work to.

   Crosslinking of ticket artifacts is currently (2021-11) missing.

   Design note: d "really should" be const here but some internals
   (d->F.cursor and delayed baseline loading) prohibit it.

   @see fsl__deck_crosslink_one()
*/
int fsl__deck_crosslink( fsl_deck /* const */ * const d );

/** @internal

   Run automatically by fsl_deck_save(), so it needn't normally be run
   aside from that, at least not from average client code.

   This is a convience form of crosslinking which must only be used
   when a single deck (and only a single deck) is to be crosslinked.
   This function wraps the crosslinking in fsl_crosslink_begin()
   and fsl__crosslink_end(), but otherwise behaves the same as
   fsl__deck_crosslink(). If crosslinking fails, any in-progress
   transaction will be flagged as failed.

   Returns 0 on success.
*/
int fsl__deck_crosslink_one( fsl_deck * const d );

/** @internal

   Checks whether the given filename is "safe" for writing to within
   f's current checkout.

   zFilename must be in canonical form: only '/' directory separators.
   If zFilename is not absolute, it is assumed to be relative to the top
   of the current checkout, else it must point to a file under the current
   checkout.

   Checks made on the filename include:

   - It must refer to a file under the current checkout.

   - Ensure that each directory listed in the file's path is actually
   a directory, and fail if any part other than the final one is a
   non-directory.

   If the name refers to something not (yet) in the filesystem, that
   is not considered an error.

   Returns 0 on success. On error f's error state is updated with
   information about the problem.
*/
int fsl__ckout_safe_file_check(fsl_cx * const f, char const * zFilename);

/** @internal
   UNTESTED!

   Creates a file named zLinkFile and populates its contents with a
   single line: zTgtFile. This behaviour corresponds to how fossil
   manages SCM'd symlink entries on Windows and on other platforms
   when the 'allow-symlinks' repo-level config setting is disabled.
   (In late 2020 fossil defaulted that setting to disabled and made it
   non-versionable.)

   zLinkFile may be an absolute path rooted at f's current checkout or
   may be a checkout-relative path.

   Returns 0 on success, non-0 on error:

   - FSL_RC_NOT_A_CKOUT if f has no opened checkout.

   - FSL_RC_MISUSE if zLinkFile refers to a path outside of the
   current checkout.

   Potential TODO (maybe!): handle symlinks as described above or
   "properly" on systems which natively support them iff f's
   'allow-symlinks' repo-level config setting is true. That said: the
   addition of symlinks support into fossil was, IMHO, a poor decision
   for $REASONS. That might (might) be reflected long-term in this API
   by only supporting them in the way fossil does for platforms which
   do not support symlinks.
*/
int fsl__ckout_symlink_create(fsl_cx * const f, char const *zTgtFile,
                              char const * zLinkFile);


/**
   Compute all file name changes that occur going from check-in iFrom
   to check-in iTo. Requires an opened repository.

   If revOK is true, the algorithm is free to move backwards in the
   chain. This is the opposite of the oneWayOnly parameter for
   fsl_vpath_shortest().

   On success, the number of name changes is written into *pnChng.
   For each name change, two integers are allocated for *piChng. The
   first is the filename.fnid for the original name as seen in
   check-in iFrom and the second is for new name as it is used in
   check-in iTo. If *pnChng is 0 then *aiChng will be NULL.

   On error returns non-0, pnChng and aiChng are not modified, and
   f's error state might (depending on the error) contain a description
   of the problem.

   Space to hold *aiChng is obtained from fsl_malloc() and must
   be released by the caller.
*/
int fsl__find_filename_changes(fsl_cx * const f,
                               fsl_id_t iFrom,
                               fsl_id_t iTo,
                               bool revOK,
                               uint32_t *pnChng,
                               fsl_id_t **aiChng);

/**
   Bitmask of file change types for use with
   fsl__is_locally_modified().
 */
enum fsl__localmod_e {
/** Sentinel value. */
FSL__LOCALMOD_NONE = 0,
/**
   Permissions changed.
*/
FSL__LOCALMOD_PERM = 0x01,
/**
   File size or hash (i.e. content) differ.
*/
FSL__LOCALMOD_CONTENT = 0x02,
/**
   The file type was switched between symlink and normal file.  In
   this case, no check for content change, beyond the file size
   change, is performed.
*/
FSL__LOCALMOD_LINK = 0x04,
/**
   File was not found in the local checkout.
 */
FSL__LOCALMOD_NOTFOUND = 0x10
};
typedef enum fsl__localmod_e fsl__localmod_e;
/** @internal

   Checks whether the given file has been locally modified compared to
   a known size, hash value, and permissions. Requires that f has an
   opened checkout.

   If zFilename is not an absolute path, it is assumed to be relative
   to the checkout root (as opposed to the current directory) and is
   canonicalized into an absolute path for purposes of this function.

   fileSize is the "original" version's file size.  zOrigHash is the
   initial hash of the file to use as a basis for comparison.
   zOrigHashLen is the length of zOrigHash, or a negative value if
   this function should use fsl_is_uuid() to determine the length. If
   the hash length is not that of one of the supported hash types,
   FSL_RC_RANGE is returned and f's error state is updated. This
   length is used to determine which hash to use for the comparison.

   If the file's current size differs from the given size, it is
   quickly considered modified, otherwise the file's contents get
   hashed and compared to zOrigHash.

   Because this is used for comparing local files to their state from
   the fossil database, where files have no timestamps, the local
   file's timestamp is never considered for purposes of modification
   checking.

   If isModified is not NULL then on success it is set to a bitmask of
   values from the fsl__localmod_e enum specifying the type(s) of
   change(s) detected:

   - FSL__LOCALMOD_PERM = permissions changed.

   - FSL__LOCALMOD_CONTENT = file size or hash (i.e. content) differ.

   - FSL__LOCALMOD_LINK = the file type was switched between symlink
     and normal file. In this case, no check for content change,
     beyond the file size change, is performed.

   - FSL__LOCALMOD_NOFOUND = file was not found in the local checkout.

   Noting that:

   - Combined values of (FSL__LOCALMOD_PERM | FSL__LOCALMOD_CONTENT) are
   possible, but FSL__LOCALMOD_NOTFOUND will never be combined with one
   of the other values.

   If stat() fails for any reason other than file-not-found
   (e.g. permissions), an error is triggered.

   Returns 0 on success. On error, returns non-0 and f's error state
   will be updated and isModified...  isNotModified. Errors include,
   but are not limited to:

   - Invalid hash length: FSL_RC_RANGE
   - f has no opened checkout: FSL_RC_NOT_A_CKOUT
   - Cannot find the file: FSL_RC_NOT_FOUND
   - Error accessing the file: FSL_RC_ACCESS
   - Allocation error: FSL_RC_OOM
   - I/O error during hashing: FSL_RC_IO

   And potentially other errors, roughly translated from errno values,
   for corner cases such as passing a directory name instead of a
   file.

   Results are undefined if any pointer argument is NULL or invalid.

   This function currently does NOT follow symlinks for purposes of
   resolving zFilename, but that behavior may change in the future or
   may become dependent on the repository's 'allow-symlinks' setting.

   Internal detail, not relevant for clients: this updates f's
   cache stat entry.
*/
int fsl__is_locally_modified(fsl_cx * const f,
                            const char * zFilename,
                            fsl_size_t fileSize,
                            const char * zOrigHash,
                            fsl_int_t zOrigHashLen,
                            fsl_fileperm_e origPerm,
                            int * isModified);

/** @internal

   This routine cleans up the state of selected cards in the given
   deck. The 2nd argument is an list of upper-case letters
   representing the cards which should be cleaned up, e.g. "ADG". If
   it is NULL, all cards are cleaned up but d has non-card state
   which is not cleaned up by this routine. Unknown letters are simply
   ignored.
*/
void fsl__deck_clean_cards(fsl_deck * const d, char const * letters);

/** @internal

    This starts a transaction (possibly nested) on the repository db
    and initializes some temporary db state needed for the
    crosslinking certain artifact types. It "should" (see below) be
    called at the start of the crosslinking process. Crosslinking
    *can* work without this but certain steps for certain (subject to
    change) artifact types will be skipped, possibly leading to
    unexpected timeline data or similar funkiness. No permanent
    SCM-relevant state will be missing, but the timeline might not be
    updated and tickets might not be fully processed. This should be
    used before crosslinking any artifact types, but will only have
    significant side effects for certain (subject to change) types.

    Returns 0 on success.

    If it returns 0 then the caller is OBLIGATED to either 1) call
    fsl__crosslink_end() or 2) call fsl_db_transaction_rollback() and
    set f->cache.isCrosslinking to false. This process may install
    temporary tables and/or triggers, so failing to call one or the
    other of those will result in misbehavior.

    @see fsl__deck_crosslink()
*/
int fsl__crosslink_begin(fsl_cx * const f);

/** @internal

    Must not be called unless fsl_crosslink_begin() has
    succeeded. This performs crosslink post-processing on certain
    artifact types and cleans up any temporary db state initialized by
    fsl__crosslink_begin().

    If the 2nd argument is not 0 then this routine triggers a rollback
    of the transaction started by fsl__crosslink_begin() and
    propagates any pending error code from f or (if f has no error
    code) from f's db handle.

    The second argument is intended to be the value of any pending
    result code (0 or otherwise) from any work done _after_
    fsl__crosslink_begin() succeeded. If passed 0, it assumes that
    there is no propagating error state and will attempt to complete
    the crosslinking process. If passed non-0, it triggers a rollback
    and unsets the f->cache.isCrosslinking flag, but does no
    additional work, then returns resultCode.

    Returns 0 on success. On error it initiates (or propagates) a
    rollback for the current transaction. If called when a rollback is
    pending, it unsets the crosslink-is-running flag and returns the
    propagating result code.
*/
int fsl__crosslink_end(fsl_cx * const f, int resultCode);


/** @internal

   Searches the current repository database for a fingerprint and
   returns it as a string in *zOut.

   If rcvid<=0 then the fingerprint matches the last entry in the
   [rcvfrom] table, where "last" means highest-numbered rcvid (as
   opposed to most recent mtime, for whatever reason). If rcvid>0 then
   it searches for an exact match.

   Returns 0 on non-error, where finding no matching rcvid causes
   FSL_RC_NOT_FOUND to be returned. If 0 is returned then *zOut will
   be non-NULL and ownership of that value is transferred to the
   caller, who must eventually pass it to fsl_free(). On error, *zOut
   is not modified.

   Returns FSL_RC_NOT_A_REPO if f has no opened repository, FSL_RC_OOM
   on allocation error, or any number of potential db-related codes if
   something goes wrong at the db level.

   This API internally first checks for "version 1" fossil
   fingerprints and falls back to "version 0" fingerprint if a v1
   fingerprint is not found. Version 0 was very short-lived and is not
   expected to be in many repositories which are accessed via this
   library. Practice has, however, revealed some.

   @see fsl_ckout_fingerprint_check()
*/
int fsl__repo_fingerprint_search(fsl_cx * const f, fsl_id_t rcvid,
                                char ** zOut);

/**
   A context for running a raw diff.
  
   The aEdit[] array describes the raw diff.  Each triple of integers in
   aEdit[] means:
  
     (1) COPY:   Number of lines aFrom and aTo have in common
     (2) DELETE: Number of lines found only in aFrom
     (3) INSERT: Number of lines found only in aTo
  
   The triples repeat until all lines of both aFrom and aTo are accounted
   for.
*/
struct fsl__diff_cx {
  /*TODO unsigned*/ int *aEdit;        /* Array of copy/delete/insert triples */
  /*TODO unsigned*/ int nEdit;         /* Number of integers (3x num of triples) in aEdit[] */
  /*TODO unsigned*/ int nEditAlloc;    /* Space allocated for aEdit[] */
  fsl_dline *aFrom;      /* File on left side of the diff */
  /*TODO unsigned*/ int nFrom;         /* Number of lines in aFrom[] */
  fsl_dline *aTo;        /* File on right side of the diff */
  /*TODO unsigned*/ int nTo;           /* Number of lines in aTo[] */
  int (*cmpLine)(const fsl_dline * const, const fsl_dline *const); /* Function to be used for comparing */
};
/**
   Convenience typeef.
*/
typedef struct fsl__diff_cx fsl__diff_cx;
/** Initialized-with-defaults fsl__diff_cx structure, intended for
    const-copy initialization. */
#define fsl__diff_cx_empty_m {\
  NULL,0,0,NULL,0,NULL,0,fsl_dline_cmp \
}
/** Initialized-with-defaults fsl__diff_cx structure, intended for
    non-const copy initialization. */
extern const fsl__diff_cx fsl__diff_cx_empty;



/** @internal

    Compute the differences between two files already loaded into
    the fsl__diff_cx structure.
   
    A divide and conquer technique is used.  We look for a large
    block of common text that is in the middle of both files.  Then
    compute the difference on those parts of the file before and
    after the common block.  This technique is fast, but it does
    not necessarily generate the minimum difference set.  On the
    other hand, we do not need a minimum difference set, only one
    that makes sense to human readers, which this algorithm does.
   
    Any common text at the beginning and end of the two files is
    removed before starting the divide-and-conquer algorithm.
   
    Returns 0 on succes, FSL_RC_OOM on an allocation error.
*/
int fsl__diff_all(fsl__diff_cx * const p);

/** @internal
 */
void fsl__diff_optimize(fsl__diff_cx * const p);

/** @internal
 */
void fsl__diff_cx_clean(fsl__diff_cx * const cx);

/** @internal

    Undocumented. For internal debugging only.
 */
void fsl__dump_triples(fsl__diff_cx const * const p,
                       char const * zFile, int ln );

/** @internal

    Removes from the BLOB table all artifacts that are in the SHUN
    table. Returns 0 on success. Requires (asserts) that a repo is
    opened.
*/
int fsl__shunned_remove(fsl_cx * const f);



/** @internal

   This is a fossil-specific internal detail not needed by the more
   generic parts of the fsl_db API. It loops through all "cached"
   prepared statements for which stmt->role has been assigned a value
   which bitmasks as true against the given role and finalizes
   them. If such a statement is currently held by a call to/via
   fsl_db_prepare_cachedv() then this will NOT finalize that
   statement, will update db's error state, and return
   FSL_RC_MISUSE.

   Returns 0 on success.

   As a special case, if role==0 then ALL cached statements are
   closed, with the caveat that the process will still fail if any
   statement is currently flagged as active.
*/
int fsl__db_cached_clear_role(fsl_db * const db, int role);

/** @internal

    Part of the crosslinking bits: rebuilds the entry for the ticket
    with the given K-card value.
*/
int fsl__ticket_rebuild(fsl_cx * const f, char const * zTktId);

/** @internal

   Calls all registered crosslink link listeners, passing each the
   given deck. Returns 0 if there are no listeners or if all return 0,
   else it propagates an error from a failed listener.

   This must only be called while crosslinking is underway.

   @see fsl_xlink_listener()
*/
int fsl__call_xlink_listeners(fsl_deck * const d);

/** @internal

   Copies symlink zFrom to a new symlink or pseudo-symlink named
   zTo.

   If realLink is true and this is a non-Windows platform,
   symlink zFrom is copied to zTo.

   If realLink is false or this is a Windows platform them...

   - On Windows this has currently undesired, or at least, highly
     arguable, behavior (historical, inherited from fossil(1)), in
     that an empty file named zTo will be created. In fossil(1) this
     function's counterpart is (apparently) never called on Windows,
     so that behavior seems to be moot. It is, however, important that
     this library never call it on Windows.

   - On non-Windows, a pseudo-symlink will be created: the string
     zFrom will be written to a regular file named zTo. That is, the
     file zTo will hold, as its contents, what it would point to if
     it were a symlink.
*/
int fsl__symlink_copy(char const *zFrom, char const *zTo, bool realLink);

/** @internal
   Clears the contents of f->cache.mcache.
*/
void fsl__cx_mcache_clear(fsl_cx * const f);

/** @internal

   Translates sqliteCode (or, if it's 0, sqlite3_errcode()) to an
   approximate FSL_RC_xxx match but treats SQLITE_ROW and SQLITE_DONE
   as non-errors (result code 0). If non-0 is returned db's error
   state is updated with the current sqlite3_errmsg() string.
*/
int fsl__db_errcode(fsl_db * const db, int sqliteCode);

/** @internal

    Clears various internal caches and resets various
    internally-cached values related to a repository db, but the data
    cleared here are not associated directly with a db handle. This is
    intended primarily to be used when a db transaction is rolled back
    which might have introduced state into those caches which would be
    stale after a rollback.
*/
void fsl__cx_clear_repo_caches(fsl_cx * const f);

/** @internal

   Plug in fsl_cx-specific db functionality into the given db handle.
   This must only be passed the MAIN db handle for the context,
   immediately after opening that handle, before f->dbMain is
   assigned.

   This function has very limited applicability and various
   preconditions which are assert()ed.
*/
int fsl__cx_init_db(fsl_cx * const f, fsl_db * const db);

/** @internal

    Attaches the given db file to f with the given role. This function "should"
    be static but we need it in repo.c when creating a new repository.

    This function has tightly-controlled preconditions which will assert
    if not met. r must be one of FSL_DBROLE_CKOUT or FSL_DBROLE_REPO.

    If createIfNotExists is true and zDbName does not exist in the
    filesystem, it is created before/as part of the OPEN or ATTACH. This is
    almost never desired, but is required for operations which create a
    repo (e.g. the aptly-named fsl_repo_create()) or a checkout db
    (e.g. fsl_repo_open_ckout()).
*/
int fsl__cx_attach_role(fsl_cx * const f, const char *zDbName,
                        fsl_dbrole_e r, bool createIfNotExists);

/** @internal

    Returns one of f->{repo,ckout}.db or NULL.

    ACHTUNG and REMINDER TO SELF: the current (2021-03) design means
    that none of these handles except for FSL_DBROLE_MAIN actually has
    an sqlite3 db handle assigned to it. This returns a handle to the
    "level of abstraction" we need to keep track of each db's name and
    db-specific other state.

    e.g. passing a role of FSL_DBROLE_CKOUT this does NOT return
    the same thing as fsl_cx_db_ckout().
*/
fsl_db * fsl__cx_db_for_role(fsl_cx * const f, fsl_dbrole_e r);

/** @internal

    Frees/clears the non-db state of f->ckout.
*/
void fsl__cx_ckout_clear(fsl_cx * const f);

/** @internal

    Maximum length of a line in a text file, in bytes. (2**15 = 32k)
*/
#define FSL__LINE_LENGTH_MASK_SZ  15

/** @internal

    Bitmask which, when AND-ed with a number, will result in the
    bottom FSL__LINE_LENGTH_MASK_SZ bits of that number.
*/
#define FSL__LINE_LENGTH_MASK     ((1<<FSL__LINE_LENGTH_MASK_SZ)-1)

#if defined(__cplusplus)
} /*extern "C"*/
#endif
#endif
/* ORG_FOSSIL_SCM_FSL_INTERNAL_H_INCLUDED */