/* -*- 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 */