/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ #if !defined(NET_FOSSIL_SCM_FSL_INTERNAL_H_INCLUDED) #define NET_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 "fossil-repo.h" /* a fossil-xxx header MUST come first b/c of config macros */ #if defined(__cplusplus) extern "C" { #endif typedef struct fsl_acache fsl_acache; typedef struct fsl_acache_line fsl_acache_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_acache cache. */ struct fsl_acache_line { /** RID of the cached record. */ fsl_id_t rid; /** Age. Newer is larger. */ fsl_int_t age; /** Content of the artifact. */ fsl_buffer content; }; /** @internal Empty-initialized fsl_acache_line structure. */ #define fsl_acache_line_empty_m { 0,0,fsl_buffer_empty_m } /** @internal A cache for tracking the existence of artifacts while the internal goings-on of control artifacts are going on. Currently the artifact cache is unused because it costs much more than it gives us. Once the library supports certain operations (like rebuild and sync) caching will become more useful. 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 hard-coded in fsl_acache_insert. Those really should be part of this struct. */ struct fsl_acache { /** Total amount of memory used by cached content. */ fsl_size_t szTotal; /** Number of entries "used" in this->list. */ uint16_t used; /** Number of slots in this->list. */ uint16_t capacity; /** Next cache counter age. Higher is newer. */ fsl_int_t nextAge; /** List of cached content, ordered by age. */ fsl_acache_line * list; /** All artifacts currently in the cache. */ fsl_id_bag inCache; /** Cache of known-missing content. */ fsl_id_bag missing; /** Cache of of known-existing content. */ fsl_id_bag available; }; /** @internal Empty-initialized fsl_acache structure, intended for const-copy initialization. */ #define fsl_acache_empty_m { \ 0/*szTotal*/,0/*used*/,0/*capacity*/, \ 0/*nextAge*/,NULL/*list*/, \ fsl_id_bag_empty_m/*inCache*/, \ fsl_id_bag_empty_m/*missing*/, \ fsl_id_bag_empty_m/*available*/ \ } /** @internal Empty-initialized fsl_acache structure, intended for copy initialization. */ extern const fsl_acache fsl_acache_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. As of this writing (20141027) the following applies: dbMain always points to &this->dbMem (a ":memory:" db opened by fsl_cx_init()), and the repo/ckout/config DBs get ATTACHed to that one. Their separate handles (this->{repo,ckout,config}.db) are used to store the name and file path to each one (even though they have no real db handle associated with them). 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_checkout(), and fsl_cx_db_config() to get a handle to the specific db they want. Currently they will always return NULL or the same handle, but that design decision might change at some point, so the public API treats them as separate entities. */ fsl_db * dbMain; /** Marker which tells us whether fsl_cx_finalize() needs to fsl_free() this instance or not. */ void const * allocStamp; /** A ":memory:" (or "") db to work around open-vs-attach-vs-main-vs-real-name problems wrt to the repo/ckout/config dbs. This db handle gets opened automatically at startup and all others which a fsl_cx manages get ATTACHed to it. Thus the other internal fsl_db objects, e.g. this->repo.db, may hold state, such as the path to the current repo db, but they do NOT hold an sqlite3 db handle. Assigning them the handle of this->dbMain would indeed simplify certain work but it would require special care to ensure that we never sqlite3_close() those out from under this->dbMain. */ fsl_db dbMem; /** Holds info directly related to a checkout database. */ struct { /** Handle to the currently opened checkout database IF the checkout is the main db. */ fsl_db db; /** Possibly not needed, but useful for doing absolute-to-relative path conversions for checking file lists. The directory part of an opened checkout db. This is currently only set by fsl_checkout_open_dir(). It contains a trailing slash, largely because that simplifies porting fossil(1) code. */ char * dir; /** Optimization: fsl_strlen() of dir. Guaranteed to be set to dir's length if dir is not NULL. */ 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. */ fsl_uuid_str uuid; } ckout; /** Holds info directly related to a repo database. */ struct { /** Handle to the currently opened repository database IF repo is the main db. */ 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 { /** Handle to the currently opened global config database IF config is the main db. */ fsl_db db; } config; /** State for incrementally proparing a checkin operation. */ struct { /** Tells us if vfile_selected has been created or not. */ int hasSetupVfile; /** 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; /** Output channel used by fsl_output() and friends. */ fsl_outputer output; /** Can be used to tie client-specific data to the context. Its finalizer is called when fsl_cx_finalize() cleans up. */ 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. This is primarily db-related routines, where we add the db-driver-provided error state here. 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; /** Optimization: reusable scratchpad for creating/encoding/decoding strings. */ fsl_buffer scratch; /** A scratchpad specifically for dealing with non-recursive filename-related resolution/normalization. */ fsl_buffer fsScratch; /** A place for temporarily holding file content. We use this in places where we have to loop over files and read their entire contents. The loop and the reading might be happening in different functions, though. */ fsl_buffer fileContent; /** A 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; /** List of callbacks for deck crosslinking purposes. */ fsl_xlinker_list xlinkers; /** A place for caching generic things. */ struct { /** If true, SOME repository-level file-name comparisons/searches will work case-insensitively. */ bool caseInsensitive; /** 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-02) yet have. */ bool ignoreDephantomizations; /** Whether or not a running commit process should be marked as private. */ 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; /** Cached copy of the allow-symlinks config option, because it is needed on each 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. We can probably default this to true, though i don't think(?) fossil(1) does so(?). 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. */ int 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. negative==undetermined, 0==no, positive==yes. This is updated when a repository is first opened and when new content is written to it. */ int seenDeltaManifest; /** Records whether this repository has an FTS search index. <0=undetermined, 0=no, >0=yes. */ int searchIndexExists; /** Record ID of rcvfrom entry during commits. This is likely to remain unused in libf, at least until/unless the sync protocol is implemented. */ fsl_id_t rcvId; /** Potential TODO: a cache of most recently loaded/freed manifests, analog to fossil's manifest.c:manifestCache array. Won't need this until we get to a point where we can rebuild or similar intensive operations. 2021-02: libf's crosslinking is much, much slower than fossil's, despite doing ostensibly the same things (albeit with a layer or two more of abstraction). This lack of cache might (might) be one factor in that. One problem we have in implementing such a cache is that libf never allocates fsl_deck dynamically because it never needs to (aside, perhaps, for purposes of this cache). Holding stack-allocated decks in the cache would be a recipe for disaster. */ struct { /** Head of the cache list. All insertions/removals happen at the head. */ fsl_deck * head; /** TODO: maximum number of entries in the mf linked list. */ uint32_t limit; /** Current number of entries in the mf linked list. */ uint32_t size; } mf; /** Artifact cache used during processing of manifests. */ fsl_acache arty; /** Used during manifest parsing to keep trace 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. */ char * projectCode; /** 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 have no business using them. */ 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; } 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; } 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*/, \ fsl_db_empty_m /* dbMem */, \ {/*ckout*/ \ fsl_db_empty_m /*db*/, \ NULL /*dir*/, 0/*dirLen*/, \ -1/*rid*/, NULL/*uuid*/ \ }, \ {/*repo*/ fsl_db_empty_m /*db*/, \ 0/*user*/ \ }, \ {/*config*/ fsl_db_empty_m /*db*/ }, \ {/*ckin*/ \ 0/*hasSetupVfile*/, \ fsl_id_bag_empty_m/*selectedIds*/, \ fsl_deck_empty_m/*mf*/ \ }, \ fsl_outputer_FILE_m /*output*/, \ fsl_state_empty_m /*clientState*/, \ fsl_error_empty_m /*error*/, \ fsl_buffer_empty_m /*scratch*/, \ fsl_buffer_empty_m /*fsScratch*/, \ fsl_buffer_empty_m /*fileContent*/, \ fsl_cx_config_empty_m /*cxConfig*/, \ FSL_CX_F_DEFAULTS/*flags*/, \ fsl_xlinker_list_empty_m/*xlinkers*/, \ {/*cache*/ \ false/*caseInsensitive*/, \ false/*ignoreDephantomizations*/, \ false/*markPrivate*/, \ false/*isCrosslinking*/, \ false/*xlinkClustersOnly*/, \ false/*inFinalVerify*/, \ -1/*allowSymlinks*/, \ -1/*seenDeltaManifest*/, \ -1/*searchIndexExists*/, \ 0/*rcvId*/, \ {/*mf*/ NULL/*head*/,7/*max*/,0/*size*/}, \ fsl_acache_empty_m/*arty*/, \ fsl_id_bag_empty_m/*mfSeen*/, \ fsl_id_bag_empty_m/*leafCheck*/, \ fsl_id_bag_empty_m/*toVerify*/, \ 0/*mtimeManifest*/, \ NULL/*projectCode*/, \ {/*globs*/ \ fsl_list_empty_m/*ignore*/, \ fsl_list_empty_m/*binary*/, \ fsl_list_empty_m/*crnl*/ \ } \ }/*cache*/, \ {/*ticket*/ \ fsl_list_empty_m/*customFields*/, \ 0/*hasTicket*/, \ 0/*hasCTime*/, \ 0/*hasChng*/, \ 0/*hasCngRid*/ \ } \ } /** @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_acache_expire_oldest(fsl_acache * 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 memory when it has finished with it. Returns 0 on success, FSL_RC_OOM on allocation error. Has undefined behaviour if !c, rid is not semantically valid, !pBlob (or pBlob has no content???). */ int fsl_acache_insert(fsl_acache * c, fsl_id_t rid, fsl_buffer *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_acache_clear(fsl_acache * c); /** @internal Checks f->cache.arty 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_acache_check_available(fsl_cx * 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 SHA1 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 * 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 * 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 * 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 * 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 * f, fsl_uuid_cstr uuid, bool isPrivate, fsl_id_t * newId ); /** @internal Recompute/rebuild the entire repo.leaf table. This can be expensive (in time) for a really large repository. So it is only done for things like a full rebuild. Returns 0 on success. Error may indicate that f is not a repo. On error f's error state may be updated. */ int fsl_repo_leaves_rebuild(fsl_cx * f); /** @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_leaf_check(fsl_cx * f, fsl_id_t pid); /** @internal Schedules a leaf check for "rid" and its parents. Returns 0 on success. */ int fsl_repo_leaf_eventually_check( fsl_cx * 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_leaf_do_pending_checks(fsl_cx *f); /** @internal Inserts a tag into f's repo db. It does not create the related control artifact - use fsl_tag_add_artifact() 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 * 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 pid to the children of pid. Returns 0 on... non-error. */ int fsl_tag_propagate_all(fsl_cx * 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. 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 *f, fsl_tagtype_e tagType, fsl_id_t pid, fsl_id_t tagid, fsl_id_t origId, const char *zValue, double mtime ); /** @internal Remove the PGP signature from a raw artifact, if there is one. Expects *pz to point to *pn bytes of string memory which might or might not be prefixed by a PGP signature. If the string is enveloped in a signature, then upon returning *pz will point to the first byte after the end of the PGP header and *pn will contain the length of the content up to, but not including, the PGP footer. If *pz does not look like a PGP header then this is a no-op. Neither pointer may be NULL and *pz must point to *pn bytes of valid memory. If *pn is initially less than 59, this is a no-op. */ void fsl_remove_pgp_signature(unsigned char const **pz, fsl_size_t *pn); /** @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 it). */ void fsl_cx_clear_mf_seen(fsl_cx * f); /** @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. */ FSL_EXPORT 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 Functionally similar to fsl_repo_filename_fnid2(), but fetches a vfile.id value. vid is the vfile.vid to filter on. If vid is <=0 then f->ckout.rid is used. It only matches on vfile.pathname, not vfile.origname, and requires an exact match because it is expected that the input filename will typically come from fsl_card_F entries. On success returns 0 and sets *rv to the vfile.id or to 0 if no entry is found. On error, returns non-0 and it should be taken seriously. In debug builds, this function asserts that no pointer arguments are NULL and that f has an opened checkout. */ int fsl_checkout_filename_vfile_id( fsl_cx * f, char const * fn, fsl_id_t vid, fsl_id_t * rv ); /** @internal Clears all fsl_buffer members of db but leaves the rest intact. If alsoErrorState is true then the error state is also cleared, else it is kept as well. */ void fsl_db_clear_strings(fsl_db * db, char 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 * 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 * 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 * 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 * 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 * 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. */ 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. */ char *fsl_db_setting_inop_rhs(); /** Hard-coded range of values of the vfile.chnged db field. */ enum fsl_vfile_change_e { FSL_VFILE_CHANGE_NONE = 0, FSL_VFILE_CHANGE_MOD = 1, FSL_VFILE_CHANGE_MERGE_MOD = 2, FSL_VFILE_CHANGE_MERGE_ADD = 3, FSL_VFILE_CHANGE_INTEGRATE_MOD = 4, FSL_VFILE_CHANGE_INTEGRATE_ADD = 5 }; typedef enum fsl_vfile_change_e fsl_vfile_change_e; /** @internal (Re)Populates f's checkout vfile table with all files from the given checkin manifest. If manifestRid is 0 or less then the current checkout's RID is used. If vfile already contains any content for the given checkin, it is left intact. Returns 0 on success, any number of codes on any number of errors. f must not be NULL and must have opened checkout and repository databases. In debug builds it will assert that that is so. Misc. side effects: - If the version is changes, any pending merge state IS DELETED (it applied to the previous vfile contents). - If the version is changed, it updates the "checkout" and "checkout-hash" config setting in the vvar table. */ int fsl_vfile_load_from_rid(fsl_cx * f, fsl_id_t manifestRid); /** @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(). FIXME: add a flag specifying whether to drop or keep existing copies. Returns 0 on success. */ int fsl_cx_ticket_create_table(fsl_cx * f); /** @internal 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, char 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 */ FSL_EXPORT int fsl_cx_ticket_load_fields(fsl_cx * f, char 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. */ FSL_EXPORT int fsl_qsort_cmp_J_cards( void const * lhs, void const * rhs ); /** @internal The internal version of fsl_deck_parse(). See that function for details regarding everything but the 3rd argument. If you happen to know the _correct_ RID for the deck being parsed, pass it as the rid argument, else pass 0. A negative value will result in a FSL_RC_RANGE error. This value is (or will be) only used as an optimization in other places and only if d->f is not NULL. Passing a positive value has no effect on how the content is parsed or on the result - it only affects internal details/optimizations. */ FSL_EXPORT int fsl_deck_parse2(fsl_deck * d, fsl_buffer * src, fsl_id_t rid); /** @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. */ FSL_EXPORT int fsl_repo_record_filename(fsl_cx * 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, by fsl_vfile_load_from_rid(), and when performing a checkin, so calling it from other code is normally not necessary. @see fsl_checkout_version_write() */ FSL_EXPORT int fsl_checkout_version_fetch( fsl_cx *f ); /** @internal Writes the 'checkout' and 'checkout-hash' properties to the currently-opened checkout db. Returns 0 on success, FSL_RC_NOT_A_CHECKOUT 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. This updates f->ckout's state to reflect the given version info. If the RID differs from f->ckout.rid then fsl_cx_checkout_version_set() is called to update f->ckout's version state. @see fsl_checkout_version_fetch() @see fsl_cx_checkout_version_set() */ FSL_EXPORT int fsl_checkout_version_write( fsl_cx *f, fsl_id_t vid, fsl_uuid_cstr uuid ); /** @internal On Windows platforms (only), if fsl_isalpha(*zFile) and ':' == zFile[1] then this returns zFile+2, otherwise it returns zFile. */ FSL_EXPORT 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_EXPORT fsl_card_F * fsl_deck_F_seek(fsl_deck * const d, const char *zName); /** @internal Part of the fsl_cx::fileContent optimization. This sets f->fileContent.used to 0 and if its capacity is over a certain (unspecified, unconfigurable) size then it is trimmed to that size. */ FSL_EXPORT void fsl_cx_yield_file_buffer(fsl_cx * f); /** @internal Expects f to have an opened checkout and zName to be the name of an entry in the vfile table. This function does not do any path resolution on zName. On success it returns 0 and assigned *vfid to the vfile.id value of the matching file. If no match is found, 0 is returned and *vfile is set to 0. This search honors the context-level case-insensitivity setting (see fsl_cx_case_sensitive_set()). */ FSL_EXPORT int fsl_filename_to_vfile_id( fsl_cx * f, char const * zName, fsl_id_t * vfid ); /** @internal Searches the vfile table where vfile.vid=vid for a name which matches zName or a subdirectory named zName. For each entry it finds, it adds the vfile.id to dest. If zName is NULL or empty then all files in vfile with the given vid are selected. This search honors the context-level case-insensitivity setting (see fsl_cx_case_sensitive_set()). Returns 0 on success. Not finding anything is not treated as an error, though we could arguably return FSL_RC_NOT_FOUND for the cases which use this function. */ FSL_EXPORT int fsl_filename_to_vfile_ids( fsl_cx * f, fsl_id_t vid, fsl_id_bag * dest, char const * zName); /** @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. */ FSL_EXPORT int fsl_search_doc_touch(fsl_cx *f, fsl_satype_e saType, fsl_id_t rid, const char * docName); /** @internal Performs the same job as fsl_diff_text() but produces the results in the low-level form of an array of "copy/delete/insert triples." This is primarily intended for internal use in other library-internal algorithms, not for client code. Note all FSL_DIFF_xxx flags apply to this form. Returns 0 on success, any number of non-0 codes on error. On success *outRaw will contain the resulting array, which must eventually be fsl_free()'d by the caller. On error *outRaw is not modified. */ FSL_EXPORT int fsl_diff_text_raw(fsl_buffer const *p1, fsl_buffer const *p2, int diffFlags, int ** outRaw); /** @internal If the given file name is a reserved filename (case-insensitive) on Windows platforms, a pointer to the reserved part of the name, else NULL is returned. 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. */ FSL_EXPORT bool fsl_is_reserved_fn_windows(const char *zPath, fsl_int_t nameLen); /** @internal Clears any pending merge state from the checkout db's vmerge table. Returns 0 on success. */ FSL_EXPORT int fsl_checkout_clear_merge_state( fsl_cx *f ); /** @internal Installs or reinstalls the checkout database schema into f's open checkout db. Returns 0 on success, FSL_RC_NOT_A_CHECKOUT 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. */ FSL_EXPORT int fsl_checkout_install_schema(fsl_cx *f, bool dropIfExists); #if defined(__cplusplus) } /*extern "C"*/ #endif #endif /* NET_FOSSIL_SCM_FSL_INTERNAL_H_INCLUDED */