fnc

Check-in Differences
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Difference From 6eaea2465f To 96e142ed8e

2021-08-28 15:43
Bump version number: version 0.2 alpha. (check-in: 39f43fcefb user: mark tags: trunk, version-0.2)
2021-08-28 15:31
Merge [9d412bdfacee14e3|libf-amalgamation] into trunk. (check-in: 96e142ed8e user: mark tags: trunk, version-0.2)
2021-08-27 14:42
Bring branch up to parity with [trunk]. (Closed-Leaf check-in: 9d412bdfac user: mark tags: libf-amalgamation)
2021-08-27 13:28
Implement simpler, more robust fix for line wrap. (check-in: b413600889 user: mark tags: trunk)
2021-08-05 11:32
Add commit branch/tags to search results. (check-in: 7dfce7ca8d user: mark tags: trunk)
2021-08-05 08:12
Initial commit of fnc. (check-in: 6eaea2465f user: mark tags: trunk)

Changes to Makefile.in.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/usr/bin/make # help out emacs
#
# Top-level autosetup-filtered Makefile for libfossil. This particular
# build is for Unix platforms with GNU Make 3.81+.
all:
.NOTPARALLEL: # stop subdir makes and reconfigure from launching
              # multiple times concurrently.
include config.make
ShakeNMake.CISH_SOURCES := $(wildcard *.c) $(wildcard $(SRC.DIR)/*.c)
# Subdir cleanup rules and deps list must come before shakenmake.make is included
# or they must be set up manually afterwards...
clean-.: clean-doc clean-fnc clean-bindings
distclean-.: distclean-doc distclean-fnc distclean-bindings
include shakenmake.make
MAIN_MAKEFILES := $(PACKAGE.MAKEFILE) $(ShakeNMake.MAKEFILE) @AUTODEPS@

AUTOCONFIG_H := @srcdir@/include/fossil-scm/autoconfig.h
#SRCDIR := @top_srcdir@/src
SRCDIR := @srcdir@/src

define CALL.SUBDIR
endef
SUBDIRS := src fnc
$(eval $(call ShakeNMake.CALL.SUBDIRS,$(SUBDIRS)))
#$(eval $(call ShakeNMake.CALL.SUBDIR,src))
subdir-fnc: subdir-src
all: subdir-src subdir-fnc


ifeq (1,@LIBFOSSIL_STATIC@)
  THELIB.LIB := libfossil$(ShakeNMake.EXTENSIONS.LIB)
else
  THELIB.LIB :=
endif
ifeq (1,@LIBFOSSIL_SHARED@)
  THELIB.DLL := libfossil$(ShakeNMake.EXTENSIONS.DLL)
else
  THELIB.DLL :=
endif

ifneq (,$(THELIB.DLL)$(THELIB.LIB))
src/$(THELIB.DLL):
	$(MAKE) -C src
src/$(THELIB.LIB):
	$(MAKE) -C src
$(THELIB.DLL): src/$(THELIB.DLL)
	ln -sf src/$(THELIB.DLL) .
$(THELIB.LIB): src/$(THELIB.LIB)
	ln -sf src/$(THELIB.LIB) .
all: $(THELIB.DLL) $(THELIB.LIB)
CLEAN_FILES += $(wildcard libfossil.so libfossil.dll libfossil.a)
endif
DISTCLEAN_FILES += config.make

########################################################################
# Other stuff...

ifeq ($(MAKE_COMPILATION_DB),yes)
all: compile_commands.json
compile_commands.json:
	@$(RM) $@
	sed -e '1s/^/[\'$$'\n''/' -e '$$s/,$$/\'$$'\n'']/' $(compdb_dir)/*.o.json > $@+
	@if test -s $@+; then mv $@+ $@; else $(RM) $@+; fi
endif

install: all
	@echo 'No installation rules yet.'

DISTCLEAN_FILES += $(AUTOCONFIG_H) Makefile config.log autosetup/jimsh0 \
	$(wildcard compile_commands/*) compile_commands.json+

# automake compatibility. do nothing for all these targets
#EMPTY_AUTOMAKE_TARGETS := dvi pdf ps info html tags ctags mostlyclean maintainer-clean check installcheck installdirs \
# install-pdf install-ps install-info install-html -install-dvi uninstall install-exec install-data distdir
#.PHONY: $(EMPTY_AUTOMAKE_TARGETS)
#$(EMPTY_AUTOMAKE_TARGETS):

## @top_srcdir@/Makefile.in: # b/c AUTODEPS contains this name (it probably shouldn't)
#$(FSL.OBJ): @AUTODEPS@ @top_srcdir@/Makefile @top_srcdir@/config.make

# Reconfigure if needed
ifeq ($(findstring clean,$(MAKECMDGOALS)),)
@top_srcdir@/config.make: @AUTODEPS@ @top_srcdir@/config.make.in
	@@AUTOREMAKE@
@top_srcdir@/Makefile: @AUTODEPS@ @top_srcdir@/Makefile.in
	chmod +w $@
	@@AUTOREMAKE@








|


|
|



<
<
<









<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
















|


<
<
<
<
<
<
<
<
<







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16



17
18
19
20
21
22
23
24
25
























26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44









45
46
47
48
49
50
51
#!/usr/bin/make # help out emacs
#
# Top-level autosetup-filtered Makefile for libfossil. This particular
# build is for Unix platforms with GNU Make 3.81+.
all:
.NOTPARALLEL: # stop subdir makes and reconfigure from launching
              # multiple times concurrently.
include config.make
ShakeNMake.CISH_SOURCES := $(wildcard *.c)
# Subdir cleanup rules and deps list must come before shakenmake.make is included
# or they must be set up manually afterwards...
clean-.: clean-fnc clean-src
distclean-.: distclean-fnc distclean-src
include shakenmake.make
MAIN_MAKEFILES := $(PACKAGE.MAKEFILE) $(ShakeNMake.MAKEFILE) @AUTODEPS@





define CALL.SUBDIR
endef
SUBDIRS := src fnc
$(eval $(call ShakeNMake.CALL.SUBDIRS,$(SUBDIRS)))
#$(eval $(call ShakeNMake.CALL.SUBDIR,src))
subdir-fnc: subdir-src
all: subdir-src subdir-fnc

























DISTCLEAN_FILES += config.make

########################################################################
# Other stuff...

ifeq ($(MAKE_COMPILATION_DB),yes)
all: compile_commands.json
compile_commands.json:
	@$(RM) $@
	sed -e '1s/^/[\'$$'\n''/' -e '$$s/,$$/\'$$'\n'']/' $(compdb_dir)/*.o.json > $@+
	@if test -s $@+; then mv $@+ $@; else $(RM) $@+; fi
endif

install: all
	@echo 'No installation rules yet.'

DISTCLEAN_FILES += Makefile config.log autosetup/jimsh0 \
	$(wildcard compile_commands/*) compile_commands.json+










# Reconfigure if needed
ifeq ($(findstring clean,$(MAKECMDGOALS)),)
@top_srcdir@/config.make: @AUTODEPS@ @top_srcdir@/config.make.in
	@@AUTOREMAKE@
@top_srcdir@/Makefile: @AUTODEPS@ @top_srcdir@/Makefile.in
	chmod +w $@
	@@AUTOREMAKE@

Changes to auto.def.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# vim:se syn=tcl:
#
use cc cc-shared cc-lib

options {
    shared=1 => "Disable build of a shared library."
    no-debug=0 => "Disable debug build options."
    amal => "Generates a conservative config file for the amalgamation build."
    static=1 => "Disable build of a static library."
    loud=0 => "Enables 'loud' build mode."
}


# autosetup interceps 'debug' and 'enable-debug' flags :/
#    prefix:=[get-env HOME /usr/local] -> "Installation prefix."

define FSL_PACKAGE_NAME "libfossil"
define FSL_LIBRARY_VERSION 0.0.1-alphabeta

########################################################################
# See if we can get the fossil schema version from the current
# checkout. If so, use that one, otherwise fall back to some hard-coded
# default.
set auxSchema {}
set contentSchema {}

set fossilBin [find-an-executable fossil]
if {[string length $fossilBin] > 0} {
    puts "Found fossil binary: $fossilBin"
    catch {
        set auxSchema [string trimright \
                       [exec echo \
                            {SELECT value FROM config WHERE name='aux-schema';} \
                            "|" $fossilBin sqlite3] ]
        set contentSchema [string trimright \
                       [exec echo \
                            {SELECT value FROM config WHERE name='content-schema';} \
                            "|" $fossilBin sqlite3] ]
    } ex
#    puts "exception=$ex"
}

#set uname [exec $fossilBin version -v "|" grep "Schema version"]
#puts "uname=$uname"
#return

if {[string length $auxSchema] == 16} {
    puts "Got aux-schema value from current repo: $auxSchema"
    puts "Got content-schema value from current repo: $contentSchema"
} else {
    set auxSchema "2015-01-24"
    # "2011-04-25 19:50"
    set contentSchema 2
    puts "Using hard-coded aux-schema: $auxSchema"
    puts "Using hard-coded content-schema: $contentSchema"
}
define FSL_AUX_SCHEMA $auxSchema
define FSL_CONTENT_SCHEMA $contentSchema

########################################################################
# Grab the code version and timestamp from manifest.uuid and manifest
# files...
#
# This requires that the repo is NOT generating delta manifests. As
# of 2020-03, the libfossil server uses the forbid-delta-manifest
# setting.
set timestamp [clock format [clock seconds] \
                   -gmt 1 -format "%Y-%m-%d %H:%M"]
set fp [open "manifest.uuid" r]
set libVersionHash [string trimright [read $fp]]
close $fp
set fp [open "manifest"]
# Get C-card:
gets $fp
# Get D-card:
gets $fp line
close $fp
unset fp
set manifestTimestamp [string map {"D " "" T " "} $line]
unset line
puts "libVersionHash = $libVersionHash"
puts "manifestTimestamp = $manifestTimestamp"

set FSL_PLATFORM_CONFIG_H "
\#define FSL_LIB_VERSION_HASH \"$libVersionHash\"
\#define FSL_LIB_VERSION_TIMESTAMP \"$manifestTimestamp UTC\"
\#define FSL_LIB_CONFIG_TIME \"$timestamp GMT\"
\#if defined(_MSC_VER)
\#define FSL_PLATFORM_OS \"windows\"
\#define FSL_PLATFORM_IS_WINDOWS 1
\#define FSL_PLATFORM_IS_UNIX 0
\#define FSL_PLATFORM_PLATFORM \"windows\"
\#define FSL_PLATFORM_PATH_SEPARATOR \";\"
\#define FSL_CHECKOUTDB_NAME \"./_FOSSIL_\"
\/* define a __func__ compatibility macro *\/
\#if _MSC_VER < 1500    /* (vc9.0; dev studio 2008) */
/* sorry; cant do much better than nothing at all on those earlier ones */
\#define __func__ \"(func)\"
\#else
\#define __func__ __FUNCTION__
\#endif
/* for the time being at least, don't complain about there being secure crt alternatives: */
\#ifndef _CRT_SECURE_NO_WARNINGS
\#define _CRT_SECURE_NO_WARNINGS
\#endif
/* for the time being at least, don't complain about using POSIX names instead of ISO C++: */
\#pragma warning ( disable : 4996 )
/* for the time being at least, suppresss some int conversion warnings */
\#pragma warning ( disable : 4244 )     /*'fsl_size_t' to 'int'; this masks other problems that should be fixed*/
\#pragma warning ( disable : 4761 )     /*'integral size mismatch in argument'; more size_t problems*/
\#pragma warning ( disable : 4267 )     /*'size_t' to 'int'; crops up especially in 64-bit builds*/
/* these were extracted from fossil's unistd.h */
\#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
\#include <io.h>
\#elif defined(__MINGW32__)
\#define FSL_PLATFORM_OS \"mingw\"
\#define FSL_PLATFORM_IS_WINDOWS 1
\#define FSL_PLATFORM_IS_UNIX 0
\#define FSL_PLATFORM_PLATFORM \"windows\"
\#define FSL_PLATFORM_PATH_SEPARATOR \";\"
\#define FSL_CHECKOUTDB_NAME \"./.fslckout\"
\#elif defined(__CYGWIN__)
\#define FSL_PLATFORM_OS \"cygwin\"
\#define FSL_PLATFORM_IS_WINDOWS 0
\#define FSL_PLATFORM_IS_UNIX 1
\#define FSL_PLATFORM_PLATFORM \"unix\"
\#define FSL_PLATFORM_PATH_SEPARATOR \":\"
\#define FSL_CHECKOUTDB_NAME \"./_FOSSIL_\"
\#else
\#define FSL_PLATFORM_OS \"unknown\"
\#define FSL_PLATFORM_IS_WINDOWS 0
\#define FSL_PLATFORM_IS_UNIX 1
\#define FSL_PLATFORM_PLATFORM \"unix\"
\#define FSL_PLATFORM_PATH_SEPARATOR \":\"
\#define FSL_CHECKOUTDB_NAME \"./.fslckout\"
\#endif
"

if {[opt-bool amal]} {
    puts "Generating conservative config for the amalgamation build..."
    set incGuard _NET_FOSSIL_SCM_FSL_AMALGAMATION_CONFIG_H_INCLUDED_
    set ofile libfossil-config.h
    set out [open $ofile w]
    puts $out "\#if !defined($incGuard)
\#define $incGuard 1
\#define FSL_AUX_SCHEMA \"$auxSchema\"
\#define FSL_CONTENT_SCHEMA \"$contentSchema\"
\#define FSL_PACKAGE_NAME \"[get-define FSL_PACKAGE_NAME]\"
\#define FSL_LIBRARY_VERSION \"[get-define FSL_LIBRARY_VERSION]\"
/* Tweak the following for your system... */
\#if !defined(HAVE_COMPRESS)
\#  define HAVE_COMPRESS 1
\#endif
\#if !defined(HAVE_DLFCN_H)
\#  define HAVE_DLFCN_H 0
\#endif
\#if !defined(HAVE_DLOPEN)
\#  define HAVE_DLOPEN 0
\#endif
\#if !defined(HAVE_GETADDRINFO)
\#  define HAVE_GETADDRINFO 0
\#endif
\#if !defined(HAVE_INET_NTOP)
\#  define HAVE_INET_NTOP 0
\#endif
\#if !defined(HAVE_INTTYPES_H)
\#  define HAVE_INTTYPES_H 0
\#endif
\#if !defined(HAVE_LIBDL)
\#  define HAVE_LIBDL 0
\#endif
\#if !defined(HAVE_LIBLTDL)
\#  define HAVE_LIBLTDL 0
\#endif
\#if !defined(_WIN32)
\#if !defined(HAVE_LSTAT)
\#  define HAVE_LSTAT 1
\#endif
\#if !defined(HAVE_LTDL_H)
\#  define HAVE_LTDL_H 0
\#endif
\#if !defined(HAVE_LT_DLOPEN)
\#  define HAVE_LT_DLOPEN 0
\#endif
\#if !defined(HAVE_OPENDIR)
\#  define HAVE_OPENDIR 1
\#endif
\#if !defined(HAVE_PIPE)
\#  define HAVE_PIPE 1
\#endif
\#if !defined(HAVE_STAT)
\#  define HAVE_STAT 1
\#endif
\#if !defined(HAVE_STDINT_H)
\#  define HAVE_STDINT_H 0
\#endif
\#if !defined(_DEFAULT_SOURCE)
\#  define _DEFAULT_SOURCE 1
\#endif
\#if !defined(_XOPEN_SOURCE)
\#  define _XOPEN_SOURCE 500
\#endif
\#else
\#if !defined(HAVE_LSTAT)
\#  define HAVE_LSTAT 0
\#endif
\#if !defined(HAVE_LTDL_H)
\#  define HAVE_LTDL_H 0
\#endif
\#if !defined(HAVE_LT_DLOPEN)
\#  define HAVE_LT_DLOPEN 0
\#endif
\#if !defined(HAVE_OPENDIR)
\#  define HAVE_OPENDIR 1
\#endif
\#if !defined(HAVE_PIPE)
\#  define HAVE_PIPE 0
\#endif
\#if !defined(HAVE_STAT)
\#  define HAVE_STAT 0
\#endif
\#if !defined(HAVE_STDINT_H)
\#  define HAVE_STDINT_H 0
\#endif
\#endif
/* _WIN32 */

$FSL_PLATFORM_CONFIG_H

\#endif
/* $incGuard */
"
    close $out
    puts "Generated $ofile."
    return
}
# end of --amal bootstrap config generation

cc-check-c11

cc-check-sizeof "void *"

if {![cc-check-includes zlib.h] ||
    ![cc-check-function-in-lib compress z]} {
    user-error "Missing functional zlib"
}
cc-check-function-in-lib iconv iconv
#if {![cc-check-functions iconv] &&
#    ![cc-check-function-in-lib iconv iconv]} {
#    user-error "Cannot find iconv(3) in libc or libiconv"
#}

########################################################################
# Checks for C99 via (__STDC_VERSION__ >= 199901L). Returns 1 if so, 0
# 0 if not.
proc cc-check-c99 {} {
    msg-checking "Checking for C99 via __STDC_VERSION__... "
    if {[cctest -code {





<

<
<







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|








<
<
<
<







1
2
3
4
5

6


7
8
9
10
11
12
13






























































































































































































































14
15
16
17
18
19
20
21
22




23
24
25
26
27
28
29
# vim:se syn=tcl:
#
use cc cc-shared cc-lib

options {

    no-debug=0 => "Disable debug build options."


    loud=0 => "Enables 'loud' build mode."
}


# autosetup interceps 'debug' and 'enable-debug' flags :/
#    prefix:=[get-env HOME /usr/local] -> "Installation prefix."































































































































































































































#cc-check-c11

cc-check-sizeof "void *"

if {![cc-check-includes zlib.h] ||
    ![cc-check-function-in-lib compress z]} {
    user-error "Missing functional zlib"
}
cc-check-function-in-lib iconv iconv





########################################################################
# Checks for C99 via (__STDC_VERSION__ >= 199901L). Returns 1 if so, 0
# 0 if not.
proc cc-check-c99 {} {
    msg-checking "Checking for C99 via __STDC_VERSION__... "
    if {[cctest -code {
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
    set CC_FLAG_C99 {}
}
if {![cc-check-c99]} {
    user-error "As of 2021-02-21, libfossil requires C99."
}

define CC_FLAG_C99 $CC_FLAG_C99
########################################################################
# Module loader is currently used only by the s2 binding.
define HAVE_LIBLTDL 0
define HAVE_LIBDL 0
define LDFLAGS_MODULE_LOADER ""
define FSL_ENABLE_MODULE_LOADER 0
if {[cc-check-includes ltdl.h] && [cc-check-function-in-lib lt_dlopen ltdl]} {
    define HAVE_LIBLTDL 1
    define LDFLAGS_MODULE_LOADER "-lltdl"
    define FSL_ENABLE_MODULE_LOADER 1
} elseif {[cc-with {-includes dlfcn.h} {
	  cctest -link 1 -declare "extern char* dlerror(void);" -code "dlerror();"}]} {
    msg-result "This system can use dlopen() w/o -ldl"
    define HAVE_LIBDL 1
    define LDFLAGS_MODULE_LOADER ""
    define FSL_ENABLE_MODULE_LOADER 1
} elseif {[cc-check-includes dlfcn.h]} {
    msg-result "Found dlfcn.h."
    define HAVE_LIBDL 1
    if {[cc-check-function-in-lib dlopen dl]} {
      msg-result "Found libdl."
      define LDFLAGS_MODULE_LOADER "-ldl"
    } else {
      msg-result "No libdl found. Assuming dlopen is built-in."
      define LDFLAGS_MODULE_LOADER ""
    }
    define FSL_ENABLE_MODULE_LOADER 1
}

if {![get-define FSL_ENABLE_MODULE_LOADER]} {
    msg-result {No usable module loading library found. No worries, because we won't have a module system yet. :-D}
} else {
    msg-result {Found a module loader. Now we just need something to do with it.}
}

########################################################################
# A proxy for cc-check-function-in-lib which "undoes" any changes that
# routine makes to the LIBS define.
proc my-check-function-in-lib {function libs {otherlibs {}}} {
    set _LIBS [get-define LIBS]
    set found [cc-check-function-in-lib $function $libs $otherlibs]
    define LIBS $_LIBS
    return $found
}

########################################################################
# readline is only used (if at all) by s2sh. If it's not available, we
# fall back to a tree-local copy of the BSD-licensed linenoise editing
# library.
if {[cc-check-includes readline/readline.h] &&
    [my-check-function-in-lib readline readline]} {
    define FSL_ENABLE_READLINE 1
    define FSL_ENABLE_LINENOISE 0
    msg-result "Enabling libreadline for f-s2sh line editing."
} else {
    msg-result "libreadline not found. f-s2sh will use the linenoise line editor."
    define FSL_ENABLE_READLINE 0
    define FSL_ENABLE_LINENOISE 1
    define lib_readline ""
}

# cc-check-functions getcwd fopen
cc-check-functions opendir stat pipe inet_ntop getaddrinfo
#msg-result [cc-check-functions lstat]

if {[cc-check-functions lstat]} {
    # for lstat() on Linux and FreeBSD:
    define _XOPEN_SOURCE 600







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<











<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







46
47
48
49
50
51
52


































53
54
55
56
57
58
59
60
61
62
63
















64
65
66
67
68
69
70
    set CC_FLAG_C99 {}
}
if {![cc-check-c99]} {
    user-error "As of 2021-02-21, libfossil requires C99."
}

define CC_FLAG_C99 $CC_FLAG_C99



































########################################################################
# A proxy for cc-check-function-in-lib which "undoes" any changes that
# routine makes to the LIBS define.
proc my-check-function-in-lib {function libs {otherlibs {}}} {
    set _LIBS [get-define LIBS]
    set found [cc-check-function-in-lib $function $libs $otherlibs]
    define LIBS $_LIBS
    return $found
}

















# cc-check-functions getcwd fopen
cc-check-functions opendir stat pipe inet_ntop getaddrinfo
#msg-result [cc-check-functions lstat]

if {[cc-check-functions lstat]} {
    # for lstat() on Linux and FreeBSD:
    define _XOPEN_SOURCE 600
369
370
371
372
373
374
375




























































































376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
if {[opt-bool no-debug]} {
    msg-result "Non-debug build."
    set cFlags "$cFlags -O2"
} else {
    msg-result "Debug build enabled. Use --no-debug to build in non-debug mode."
    set cFlags "$cFlags -g -DDEBUG -O0"
}





























































































puts "Checking for compile_commands.json support..."
if {[cctest -lang c -cflags {/dev/null -MJ} -source {}]} {
    msg-result "Compiler supports compile_commands.json."
    define MAKE_COMPILATION_DB yes
} else {
    msg-result "Compiler does not support compile_commands.json."
    define MAKE_COMPILATION_DB no
}

define CFLAGS $cFlags

if {[opt-bool shared]} {
    msg-result "Enabling build of shared library."
    define LIBFOSSIL_SHARED 1
} else {
    define LIBFOSSIL_SHARED 0
    msg-result "Disabling build of shared library."
}

if {[opt-bool static]} {
    msg-result "Enabling build of static library."
    define LIBFOSSIL_STATIC 1
} else {
    define LIBFOSSIL_STATIC 0
    msg-result "Disabling build of static library."
}


if {[opt-bool loud]} {
    define BUILD_QUIETLY 0
    puts "Using 'loud' build mode."
} else {
    define BUILD_QUIETLY 1
    puts "Enabling quiet build mode. Use --loud to enable loud mode."
}

set dotBin [find-an-executable dot]
if {[string length $dotBin]} {
    define DOXYGEN_HAVE_DOT YES
    msg-result "Adding HAVE_DOT=YES to doxyfile."
} else {
    define DOXYGEN_HAVE_DOT NO
}


# Creates mkefile(-like) file $name from $name.in but explicitly makes
# the output read-only, to avoid inadvertent editing (who, me?).
proc makeFromDotIn {name} {
    catch { exec chmod u+w $name }
    make-template $name.in $name
    catch { exec chmod u-w $name }
}
# Each generated Makefile requires an input file with a .in extension:
set makefiles {
    config.make
    Makefile
    doc/Doxyfile
    src/Makefile
    fnc/Makefile
    bindings/Makefile
    bindings/cpp/Makefile
}
foreach {f} $makefiles {
    makeFromDotIn $f
}

if {0} {
    # Achtung: ordering of the -bare/-str options here is important
    # because of the mixed use of strings and integers for #defines...
    make-config-header include/fossil-scm/autoconfig.h \
        -none {DOXYGEN_*} \
        -bare {HAVE_*  FSL_ENABLE_* _DEFAULT_SOURCE _XOPEN_SOURCE} \
        -str {FSL_* PACKAGE_*}
}

########################################################################
# Generate our autconf header by hand to allow finer control
# over the structure....
set confH include/fossil-scm/autoconfig.h
set incGuard _NET_FOSSIL_SCM_FSL_AUTO_CONFIG_H_INCLUDED_
if {[opt-bool amal]} {
    set confH libfossil-config.h
    set incGuard _NET_FOSSIL_SCM_FSL_AMALGAMATION_CONFIG_H_INCLUDED_
}
puts "Generating config header $confH"
    set out [open $confH w]
    puts $out "\#if !defined($incGuard)
\#define $incGuard 1
\#define FSL_AUX_SCHEMA \"$auxSchema\"
\#define FSL_CONTENT_SCHEMA \"$contentSchema\"
\#define FSL_PACKAGE_NAME \"[get-define FSL_PACKAGE_NAME]\"
\#define FSL_LIBRARY_VERSION \"[get-define FSL_LIBRARY_VERSION]\"
\#define FSL_SHA1_HARDENED 1
/* Tweak the following for your system... */
\#if !defined(HAVE_GETADDRINFO)
\#  define HAVE_GETADDRINFO [get-define HAVE_GETADDRINFO 0]
\#endif
\#if !defined(HAVE_INET_NTOP)
\#  define HAVE_INET_NTOP [get-define HAVE_INET_NTOP 0]
\#endif
\#if !defined(_WIN32)
\#if !defined(HAVE_DLFCN_H)
\#  define HAVE_DLFCN_H [get-define HAVE_DLFCN_H 0]
\#endif
\#if !defined(HAVE_DLOPEN)
\#  define HAVE_DLOPEN [get-define HAVE_DLOPEN 0]
\#endif
\#if !defined(HAVE_LIBDL)
\#  define HAVE_LIBDL [get-define HAVE_LIBDL 0]
\#endif
\#if !defined(HAVE_LIBLTDL)
\#  define HAVE_LIBLTDL [get-define HAVE_LIBLTDL 0]
\#endif
\#if !defined(HAVE_LSTAT)
\#  define HAVE_LSTAT [get-define HAVE_LSTAT 1]
\#endif
\#if !defined(HAVE_LTDL_H)
\#  define HAVE_LTDL_H [get-define HAVE_LTDL_H 0]
\#endif
\#if !defined(HAVE_LT_DLOPEN)
\#  define HAVE_LT_DLOPEN [get-define HAVE_LT_DLOPEN 0]
\#endif
\#if !defined(HAVE_OPENDIR)
\#  define HAVE_OPENDIR [get-define HAVE_OPENDIR 1]
\#endif
\#if !defined(HAVE_PIPE)
\#  define HAVE_PIPE [get-define HAVE_PIPE 1]
\#endif
\#if !defined(HAVE_STAT)
\#  define HAVE_STAT [get-define HAVE_STAT 1]
\#endif
\#if !defined(_DEFAULT_SOURCE)
\#  define _DEFAULT_SOURCE [get-define _DEFAULT_SOURCE 1]
\#endif
\#if !defined(_XOPEN_SOURCE)
\#  define _XOPEN_SOURCE [get-define _XOPEN_SOURCE 500]
\#endif
\#else
/* _WIN32: */
\#if !defined(HAVE_DLFCN_H)
\#  define HAVE_DLFCN_H 0
\#endif
\#if !defined(HAVE_DLOPEN)
\#  define HAVE_DLOPEN 0
\#endif
\#if !defined(HAVE_LIBDL)
\#  define HAVE_LIBDL 0
\#endif
\#if !defined(HAVE_LIBLTDL)
\#  define HAVE_LIBLTDL 0
\#endif
\#if !defined(HAVE_LSTAT)
\#  define HAVE_LSTAT 0
\#endif
\#if !defined(HAVE_LTDL_H)
\#  define HAVE_LTDL_H 0
\#endif
\#if !defined(HAVE_LT_DLOPEN)
\#  define HAVE_LT_DLOPEN 0
\#endif
\#if !defined(HAVE_OPENDIR)
\#  define HAVE_OPENDIR 1
\#endif
\#if !defined(HAVE_PIPE)
\#  define HAVE_PIPE 0
\#endif
\#if !defined(HAVE_STAT)
\#  define HAVE_STAT 0
\#endif
\#endif
/*_WIN32*/

$FSL_PLATFORM_CONFIG_H

\#endif
/* $incGuard */
"
    close $out
    puts "Generated $confH."
    return
}







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>











<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<








<
<
<
<
<
<
<
<
<












<


<
<













<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199

















200
201
202
203
204
205
206
207









208
209
210
211
212
213
214
215
216
217
218
219

220
221


222
223
224
225
226
227
228
229
230
231
232
233
234











































































































if {[opt-bool no-debug]} {
    msg-result "Non-debug build."
    set cFlags "$cFlags -O2"
} else {
    msg-result "Debug build enabled. Use --no-debug to build in non-debug mode."
    set cFlags "$cFlags -g -DDEBUG -O0"
}


set pcBin [find-an-executable pkg-config]
if {"" eq $pcBin} {
    puts {pkg-config not found, so making some guesses about
available packages.}
}
########################################################################
# Curses!
set LIB_CURSES ""
set CFLAGS_CURSES ""
puts "Looking for \[n]curses..."
if {"" ne $pcBin && $::tcl_platform(os)!="Darwin"} {
# Some macOS pkg-config configurations alter library search paths, which make
# the compiler unable to find lib iconv, so don't use pkg-config on macOS.
    set np ""
    foreach p {ncursesw ncurses} {
        if {[catch {exec $pcBin --exists $p}]} {
            continue
        }
        set np $p
        puts "Using pkg-config curses package \[$p]"
        break
    }
    if {"" ne $np} {
        set ppanel ""
        if {"ncursesw" eq $np} {
            if {![catch {exec $pcBin --exists panelw}]} {
                set ppanel panelw
            }
        }
        if {"" eq $ppanel && ![catch {exec $pcBin --exists panel}]} {
            set ppanel panel
        }
        set CFLAGS_CURSES [exec $pcBin --cflags $np]
        set LIB_CURSES [exec $pcBin --libs $np]
        if {"" eq $ppanel} {
            # Apparently Mac brew has pkg-config for ncursesw but not
            # panel/panelw, but hard-coding -lpanel seems to work on
            # that platform.
            append LIB_CURSES " -lpanel"
        } else {
            append LIB_CURSES " " [exec $pcBin --libs $ppanel]
            # append CFLAGS_CURSES " " [exec $pcBin --cflags $ppanel]
            # ^^^^ appending the panel cflags will end up duplicating
            # at least one -D flag from $np's cflags, leading to
            # "already defined" errors at compile-time. Sigh. Note, however,
            # that $ppanel's cflags have flags which $np's do not, so we
            # may need to include those flags anyway and manually perform
            # surgery on the list to remove dupes. Sigh.
        }
    }
}

if {"" eq $LIB_CURSES} {
    puts "Guessing curses location (will fail for exotic locations)..."
    define HAVE_CURSES_H [cc-check-includes curses.h]
    if {[get-define HAVE_CURSES_H]} {
        # Linux has -lncurses, BSD -lcurses. Both have <curses.h>
        msg-result "Found curses.h"
        if {[my-check-function-in-lib waddnwstr ncursesw]} {
            msg-result "Found -lncursesw"
            set LIB_CURSES "-lncursesw -lpanelw"
        } elseif {[my-check-function-in-lib initscr ncurses]} {
            msg-result "Found -lncurses"
            set LIB_CURSES "-lncurses -lpanel"
        } elseif {[my-check-function-in-lib initscr curses]} {
            msg-result "Found -lcurses"
            set LIB_CURSES "-lcurses -lpanel"
        }
    }
}
if {"" eq $LIB_CURSES} {
    user-error "!Curses! Foiled again!"
} else {
    puts {
        ************************************************************
        If your build fails due to missing functions such as
        waddwstr(), make sure you have the ncursesW development
        package installed. Some platforms combine the "w" and non-w
        curses builds and some don't.

        The package may have a name such as libncursesw5-dev or
        some such.
        ************************************************************
    }
}
define LIB_CURSES $LIB_CURSES
define CFLAGS_CURSES $CFLAGS_CURSES
unset LIB_CURSES
unset CFLAGS_CURSES


puts "Checking for compile_commands.json support..."
if {[cctest -lang c -cflags {/dev/null -MJ} -source {}]} {
    msg-result "Compiler supports compile_commands.json."
    define MAKE_COMPILATION_DB yes
} else {
    msg-result "Compiler does not support compile_commands.json."
    define MAKE_COMPILATION_DB no
}

define CFLAGS $cFlags


















if {[opt-bool loud]} {
    define BUILD_QUIETLY 0
    puts "Using 'loud' build mode."
} else {
    define BUILD_QUIETLY 1
    puts "Enabling quiet build mode. Use --loud to enable loud mode."
}










# Creates mkefile(-like) file $name from $name.in but explicitly makes
# the output read-only, to avoid inadvertent editing (who, me?).
proc makeFromDotIn {name} {
    catch { exec chmod u+w $name }
    make-template $name.in $name
    catch { exec chmod u-w $name }
}
# Each generated Makefile requires an input file with a .in extension:
set makefiles {
    config.make
    Makefile

    src/Makefile
    fnc/Makefile


}
foreach {f} $makefiles {
    makeFromDotIn $f
}

if {0} {
    # Achtung: ordering of the -bare/-str options here is important
    # because of the mixed use of strings and integers for #defines...
    make-config-header include/fossil-scm/autoconfig.h \
        -none {DOXYGEN_*} \
        -bare {HAVE_*  FSL_ENABLE_* _DEFAULT_SOURCE _XOPEN_SOURCE} \
        -str {FSL_* PACKAGE_*}
}











































































































Deleted bindings/Makefile.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/make # help out emacs
all:
SUBDIRS :=
# Subdir cleanup rules and deps list must come before shakenmake.make is included
# or they must be set up manually afterwards...
ifneq (,$(CXX))
clean-.: clean-cpp
distclean-.: distclean-cpp
SUBDIRS += cpp
all: subdir-cpp
endif

clean-.: clean-s2
distclean-.: distclean-s2
include ../subdir-inc.make
SUBDIRS += s2
$(eval $(call ShakeNMake.CALL.SUBDIRS,$(SUBDIRS)))
all: subdir-s2
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




































Deleted bindings/Makefile.in.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/make # help out emacs
all:
SUBDIRS :=
# Subdir cleanup rules and deps list must come before shakenmake.make is included
# or they must be set up manually afterwards...
ifneq (,$(CXX))
clean-.: clean-cpp
distclean-.: distclean-cpp
SUBDIRS += cpp
all: subdir-cpp
endif

clean-.: clean-s2
distclean-.: distclean-s2
include ../subdir-inc.make
SUBDIRS += s2
$(eval $(call ShakeNMake.CALL.SUBDIRS,$(SUBDIRS)))
all: subdir-s2
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




































Deleted bindings/cpp/Context.cpp.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp"
#include <cassert>

/* only for debugging */
#include <iostream>
#define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : "

namespace fsl {

  Context::~Context(){
    if(this->ownsCx){
      fsl_cx_finalize(this->f);
    }
  }

  void Context::setup(fsl_cx_init_opt const * opt){
    //this->f = fsl_cx_malloc();
    //if(!this->f) throw OOMException();
    assert(!this->f);
    int const rc = fsl_cx_init(&this->f, opt);
    if(rc){
      fsl_error err = fsl_error_empty;
      if(this->f){
        fsl_error_move( &this->f->error, &err );
        fsl_cx_finalize(this->f);
        this->f = NULL;
      }else{
        fsl_error_set( &err, rc,
                       "fsl_cx_init() failed with code %s",
                       fsl_rc_cstr(rc) );
      }
      throw Exception(err);
    }
  }

  Context::Context()
    : f(NULL),
      ownsCx(true),
      dbCkout(),
      dbRe(),
      dbMain()
  {
    this->setup(NULL);
  }
  
  Context::Context(fsl_cx_init_opt const & opt)
    : f(NULL),
      ownsCx(true),
      dbCkout(),
      dbRe(),
      dbMain()
  {
    this->setup(&opt);
  }

  Context::Context(fsl_cx * f, bool ownsHandle)
    : f(f),
      ownsCx(ownsHandle),
      dbCkout(),
      dbRe(),
      dbMain()
  {
    
  }

  Context::operator fsl_cx * () throw(){
    return this->f;
  }

  Context::operator fsl_cx const * () const throw() {
    return this->f;
  }

  void Context::propagateError() const{
    if(this->f){
      fsl_error const * err = fsl_cx_err_get_e(this->f);
      if(err->code) throw Exception(err);
    }
  }

  void Context::assertRC(char const * context, int rc) const{
    if(rc){
      this->propagateError();
      throw Exception(rc, "%s: %s", context, fsl_rc_cstr(rc));
    }
  }

  void Context::assertHasRepo(){
    assert(this->f);
    if(!fsl_needs_repo(this->f)){
      fsl_error const * err = fsl_cx_err_get_e(this->f);
      assert(err->code);
      throw Exception(err);
    }
  }

  void Context::assertHasCheckout(){
    assert(this->f);
    if(!fsl_needs_ckout(this->f)){
      fsl_error const * err = fsl_cx_err_get_e(this->f);
      assert(err->code);
      throw Exception(err);
    }
  }

  fsl_cx * Context::handle() throw(){
    return this->f;
  }

  fsl_cx const * Context::handle() const throw(){
    return this->f;
  }

  Db & Context::db() throw() {
    if(!this->dbMain.handle()){
      fsl_db * db = fsl_cx_db(*this);
      if(db) this->dbMain.handle(db, false);
    }
    return this->dbMain;
  }


  Db & Context::dbRepo() throw() {
    if(!this->dbRe.handle()){
      fsl_db * db = fsl_cx_db_repo(*this);
      if(db) this->dbRe.handle(db, false);
    }
    return this->dbRe;
  }

  Db & Context::dbCheckout() throw() {
    if(!this->dbCkout.handle()){
      fsl_db * db = fsl_cx_db_ckout(*this);
      if(db) this->dbCkout.handle(db, false);
    }
    return this->dbCkout;
  }

  Context & Context::openCheckout( char const * dirName ){
    this->assertRC( "openCheckout()",
                    fsl_ckout_open_dir(this->f, dirName, true) );
    return *this;
  }

  Context & Context::openRepo( char const * dbName ){
    this->assertRC( "openRepo()",
                    fsl_repo_open(this->f, dbName) );
    return *this;
  }

  Context & Context::closeDbs() throw(){
    fsl_cx_close_dbs(this->f)
      /*
        Reminder to self: the 3 fsl_cx db handles are stored as
        complete fsl_db instances (not pointers) in fsl_cx, with the
        exception of the "main" db, which is just a pointer to one of
        the other 3. What does that mean? It means that when we use
        fsl_cx_close_dbs(), this->dbRe and friends will (if
        initialized) still be pointing to those pointers...  which are
        (due to internal details) actually still valid, they just
        refer to closed fsl_db handles.

        That's actually good for us here, except that certain
        combinations of C-level ops "might" get our checkout/repo db
        pointers cross a bit.
      */
      ;
    assert(!this->dbRe.ownsHandle());
    assert(!this->dbMain.ownsHandle());
    assert(!this->dbCkout.ownsHandle());
    this->dbRe.close();
    this->dbCkout.close();
    this->dbMain.close();
    if(this->dbRe.handle()){
      assert(!this->dbRe.handle()->dbh);
    }
    return *this;
  }

  bool Context::ownsHandle() const throw(){
    return this->ownsCx;
  }

  std::string Context::ridToArtifactUuid(fsl_id_t rid,
                                         fsl_satype_e type){
    this->assertHasRepo();
    fsl_uuid_str uuid = fsl_rid_to_artifact_uuid(*this, rid, type);
    if(!uuid){
      this->propagateError();
      throw Exception(FSL_RC_NOT_FOUND,
                      "Could not resolve RID %" FSL_ID_T_PFMT
                      " as artifact type %s.",
                      (fsl_id_t)rid, fsl_satype_cstr(type));
    }
    std::string const & rc = uuid;
    fsl_free(uuid);
    return rc;
  }

  std::string Context::ridToUuid(fsl_id_t rid){
    this->assertHasRepo();
    fsl_uuid_str uuid = fsl_rid_to_uuid(*this, rid);
    if(!uuid){
      this->propagateError();
      throw Exception(FSL_RC_NOT_FOUND, "Could not resolve RID %" FSL_ID_T_PFMT ".",
                      (fsl_id_t)rid);
    }
    std::string const & rc = uuid;
    fsl_free(uuid);
    return rc;
  }

  std::string Context::symToUuid(char const * symbolicName,
                                 fsl_id_t * rid,
                                 fsl_satype_e type){
    this->assertHasRepo();
    fsl_uuid_str uuid = NULL;
    int const rc = fsl_sym_to_uuid(*this, symbolicName, type, &uuid, rid);
    if(rc){
      this->propagateError();
      throw Exception(rc);
    }
    std::string const & rv = uuid;
    fsl_free(uuid);
    return rv;
  }


  fsl_id_t Context::symToRid(char const * symbolicName, fsl_satype_e type){
    this->assertHasRepo();
    fsl_id_t rv = 0;
    int const rc = fsl_sym_to_rid(*this, symbolicName, type, &rv);
    if(rc){
      this->propagateError();
      throw Exception(rc);
    }
    assert(rv>0);
    return rv;
  }

  fsl_id_t Context::symToRid(std::string const & symbolicName,
                             fsl_satype_e type){
    return this->symToRid( symbolicName.c_str(), type );
  }

  Context::Transaction::Transaction(Context &cx)
    : tr( cx.db() ),
      level(tr.level()){
  }

  Context::Transaction::~Transaction() throw(){
    if(this->level) this->tr.rollback();
  }

  void Context::Transaction::commit(){
    if(this->level){
      this->level = 0;
      this->tr.commit();
    }else{
      throw Exception(FSL_RC_MISUSE,
                      "commit() called multiple times.");
    }
  }

  Context & Context::getContent( fsl_id_t rid, Buffer & dest ){
    int const rc = fsl_content_get( *this, rid, dest );
    if(rc){
      this->propagateError();
      throw Exception(rc);
    }
    return *this;
  }

  Context & Context::getContent( char const * sym, Buffer & dest ){
    int const rc = fsl_content_get_sym( *this, sym, dest );
    if(rc){
      this->propagateError();
      throw Exception(rc);
    }
    return *this;
  }

  Context & Context::getContent( std::string const & sym, Buffer & dest ){
    return this->getContent( sym.c_str(), dest );
  }

} // namespace fsl

#undef CERR
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






































































































































































































































































































































































































































































































































































































Deleted bindings/cpp/Db.cpp.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp"
#include <cassert>

/* only for debugging */
#include <iostream>
#define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : "

namespace fsl {
  Stmt::~Stmt() throw(){
    fsl_stmt_finalize(*this);
  }

  Stmt::Stmt(Db & db) throw() :
    db(db),
    stmt(fsl_stmt_empty){
  }

  Stmt::operator fsl_stmt * () throw(){
    return &this->stmt;
  }

  Stmt::operator fsl_stmt const * () const throw() {
    return &this->stmt;
  }


  void Stmt::assertPrepared() const{
    if(!this->stmt.stmt){
      throw Exception(FSL_RC_MISUSE,
                      "Statement is not prepared.");
    }
    assert(this->stmt.db);
  }

  void Stmt::assertRange(short col, short base) const{
    this->assertPrepared();
    char const * errMsg = NULL;
    if(col<0){
      errMsg = "column/parameter index %d is invalid";
    }else if(base){
      assert(1==base);
      if(!col || col > fsl_stmt_param_count(*this)){
        errMsg = "parameter index %d is out of range";
      }
    }else{
      assert(0==base);
      if(col >= fsl_stmt_col_count(*this)){
        errMsg = "column index %d is out of range";
      }
    }
    if(errMsg) throw Exception(FSL_RC_RANGE, errMsg, col+base);
  }

  void Stmt::propagateError() const{
    this->db.propagateError();
  }

  void Stmt::assertRC(char const * context, int rc) const{
    if(rc){
      this->propagateError();
      throw Exception(rc, "%s: %s", context, fsl_rc_cstr(rc));
    }
  }

  Stmt & Stmt::prepare(std::string const & sql){
    return this->prepare("%s", sql.c_str());
  }

  Stmt & Stmt::prepare(Buffer const & sql){
    return this->prepare("%s", sql.c_str());
  }

  Stmt & Stmt::prepare(char const * sql, ... ){
    va_list vargs;
    if(this->stmt.stmt) throw Exception(FSL_RC_MISUSE,
                                        "Statement is prepared and not "
                                        "yet finalized. Cowardly refusing "
                                        "to re-prepare() non-finalized statement: %s",
                                        this->sql());
    int rc = 0;    
    va_start(vargs,sql);
    rc = fsl_db_preparev( this->db.handle(), *this,
                          sql, vargs );
    va_end(vargs);
    if(rc){
      this->propagateError();
      throw Exception(rc,"SQL preparation failed for: %s", sql);
    }else{
      assert(this->stmt.db == this->db.handle());
      return *this;
    }
  }

  int Stmt::stepCount() const throw(){
    return this->stmt.rowCount;
  }

  int Stmt::paramCount() const throw(){
    return this->stmt.paramCount;
  }

  int Stmt::columnCount() const throw(){
    return this->stmt.colCount;
  }

  char const * Stmt::sql() const throw() {
    return stmt.stmt
      ? fsl_buffer_cstr(&stmt.sql)
      : NULL;
  }

  fsl_stmt * Stmt::handle() throw(){
    return &this->stmt;
  }
  
  fsl_stmt const * Stmt::handle() const throw() {
    return &this->stmt;
  }

  Stmt & Stmt::reset(bool resetStepCounterToo){
    this->assertRC("reset()",
                   fsl_stmt_reset2(*this,
                                   resetStepCounterToo));
    return *this;
  }

  Stmt & Stmt::finalize() throw(){
    fsl_stmt_finalize(*this);
    return *this;
  }

  bool Stmt::step(){
    this->assertPrepared();
    int const rc = fsl_stmt_step(*this);
    switch(rc){
      case FSL_RC_STEP_ROW: return true;
      case FSL_RC_STEP_DONE: return false;
      default:
        this->propagateError();
        throw Exception(rc,"No idea what went wrong.");
    }
  }

  Stmt & Stmt::stepExpectDone(){
    if(this->step()){
      throw Exception(FSL_RC_ERROR,
                      "Expecting statement to return no rows: %s",
                      this->sql());
    }
    return *this;
  }

  int32_t Stmt::getInt32(short col){
    this->assertRange(col, 0);
    return fsl_stmt_g_int32( *this, col );
  }

  int64_t Stmt::getInt64(short col){
    this->assertRange(col, 0);
    return fsl_stmt_g_int64( *this, col );
  }

  double Stmt::getDouble(short col){
    this->assertRange(col, 0);
    return fsl_stmt_g_double( *this, col );
  }

  fsl_id_t Stmt::getId(short col){
    this->assertRange(col, 0);
    return fsl_stmt_g_id( *this, col );
  }

  char const * Stmt::columnName(short col){
    this->assertRange(col, 0);
    return fsl_stmt_col_name(*this, col);
  }

  char const * Stmt::getText(short col, fsl_size_t * length){
    this->assertRange(col, 0);
    return fsl_stmt_g_text(*this, col, length);
  }

  void const * Stmt::getBlob(short col, fsl_size_t * length){
    void const * v = NULL;
    this->assertRange(col, 0);
    fsl_stmt_get_blob(*this, col, &v, length);
    return v;
  }

  Stmt & Stmt::bind(short col){
    this->assertRange(col, 1);
    this->assertRC("bind NULL",
                   fsl_stmt_bind_null(*this, col));
    return *this;
  }
  
  Stmt & Stmt::bind(short col, int32_t v){
    this->assertRange(col, 1);
    this->assertRC("bind int32",
                   fsl_stmt_bind_int32(*this, col, v));
    return *this;
  }

  Stmt & Stmt::bind(short col, int64_t v){
    this->assertRange(col, 1);
    this->assertRC("bind int64",
                   fsl_stmt_bind_int64(*this, col, v));
    return *this;
  }

  Stmt & Stmt::bind(short col, double v){
    this->assertRange(col, 1);
    this->assertRC("bind double",
                   fsl_stmt_bind_double(*this, col, v));
    return *this;
  }

  Stmt & Stmt::bind(short col, char const * str,
                    fsl_int_t len, bool copyBytes){
    this->assertRange(col, 1);
    this->assertRC("bind text",
                   fsl_stmt_bind_text(*this, col, str, len, copyBytes));
    return *this;
  }

  Stmt & Stmt::bind(short col, std::string const & str){
    return this->bind(col, str.c_str(), (fsl_int_t)str.size(), 1);
  }
  
  Stmt & Stmt::bind(short col, void const * v,
                    fsl_size_t len, bool copyBytes){
    this->assertRange(col, 1);
    this->assertRC("bind blob",
                   fsl_stmt_bind_blob(*this, col, v, len, copyBytes));
    return *this;
  }

  int Stmt::paramIndex(char const * name){
    this->assertPrepared();
    return fsl_stmt_param_index(*this, name);
  }

  Stmt & Stmt::bind(char const * col){
    this->assertRC("bind NULL",
                   fsl_stmt_bind_null_name(*this, col));
    return *this;
  }
  
  Stmt & Stmt::bind(char const * col, int32_t v){
    this->assertRC("bind int32",
                   fsl_stmt_bind_int32_name(*this, col, v));
    return *this;
  }

  Stmt & Stmt::bind(char const * col, int64_t v){
    this->assertRC("bind int64",
                   fsl_stmt_bind_int64_name(*this, col, v));
    return *this;
  }

  Stmt & Stmt::bind(char const * col, double v){
    this->assertRC("bind double",
                   fsl_stmt_bind_double_name(*this, col, v));
    return *this;
  }

  Stmt & Stmt::bind(char const * col, char const * str,
                    fsl_int_t len, bool copyBytes){
    this->assertRC("bind text",
                   fsl_stmt_bind_text_name(*this, col, str, len, copyBytes));
    return *this;
  }

  Stmt & Stmt::bind(char const * col, std::string const & str){
    return this->bind(col, str.c_str(), (fsl_int_t)str.size(), 1);
  }
  
  Stmt & Stmt::bind(char const * col, void const * v,
                    fsl_size_t len, bool copyBytes){
    this->assertRC("bind blob",
                   fsl_stmt_bind_blob_name(*this, col, v, len, copyBytes));
    return *this;
  }

  StmtBinder::~StmtBinder(){}

  StmtBinder::StmtBinder(Stmt &s) : st(s), col(0)
  {}

  Stmt & StmtBinder::stmt(){
    return this->st;
  }

  StmtBinder & StmtBinder::operator()(){
    st.bind(++this->col);
    return *this;
  }

  StmtBinder & StmtBinder::operator()(char const * v, fsl_int_t len,
                                      bool copyBytes){
    st.bind(++this->col, v, len, copyBytes);
    return *this;
  }

  StmtBinder & StmtBinder::operator()(void const * v, fsl_size_t len,
                                      bool copyBytes){
    st.bind(++this->col, v, len, copyBytes);
    return *this;
  }

  StmtBinder & StmtBinder::reset(bool alsoStatement) {
    this->col = 0;
    if(alsoStatement) this->st.reset();
    return *this;
  }

  bool StmtBinder::step(){
    return this->st.step();
  }

  StmtBinder & StmtBinder::once(){
    this->st.stepExpectDone();
    return this->reset();
  }

  Db::~Db() throw(){
    this->close();
  }

  void Db::setup(){
    assert(!this->db);
    this->db = fsl_db_malloc();
    if(!this->db) throw OOMException();
    this->ownsDb = true;
  }

  Db::Db(): db(NULL),
            ownsDb(true){
  }

  Db::Db(char const * filename, int openFlags)
    : db(NULL),
      ownsDb(true){
    try{
      this->open(filename, openFlags);
    }catch(...){
      if(this->db) fsl_db_close(this->db);
      throw;
    }
  }

  void Db::propagateError() const{
    if(this->db && this->db->error.code){
      throw Exception(this->db->error);
    }
  }

  void Db::assertRC(char const * context, int rc) const{
    if(rc){
      this->propagateError();
      throw Exception(rc, "%s: %s", context, fsl_rc_cstr(rc));
    }
  }

  void Db::assertOpened() const{
    if(!this->db || !this->db->dbh){
      throw Exception(FSL_RC_MISUSE,
                      "Db is not opened.");
    }
  }

  Db::operator fsl_db * () throw(){
    return this->db;
  }

  Db::operator fsl_db const * () const throw() {
    return this->db;
  }
  
  Db & Db::handle( fsl_db * db, bool ownsHandle )throw(){
    if(this->db != db){
      this->close();
      this->db = db;
      this->ownsDb = ownsHandle;
    }
    return *this;
  }

  Db & Db::close() throw(){
    if(this->db){
      if(this->ownsDb){
        fsl_db_close(*this);
      }
      this->db = NULL;
    }
    return *this;
  }

  char const * Db::filename() throw(){
    return this->db
      ? fsl_db_filename(*this, NULL)
      : NULL;
  }

  Db & Db::open(char const * filename, int openFlags){
    if(!this->db) this->setup();
    else if(this->db->dbh){
      throw Exception(FSL_RC_MISUSE,
                      "Db is already opened: %s",
                      this->filename());
    }
    int const rc = fsl_db_open(*this, filename, openFlags);
    if(rc){
      /*
         The problem here is that open() can be called from the ctor,
         and if the ctor throws then the dtor is not called, so we
         have to free up this->db. Oh, wait...  the open() ctor does
         that for us, so this gets easier.

         But... if it throws from outside the ctor then we DO have to
         clean up.
       */
      Exception const & ex = this->db->error.code
        ? Exception(this->db->error)
        : Exception(rc,"fsl_db_open(%s) failed: %s",
                    filename, fsl_rc_cstr(rc));
      this->close();
      throw ex;
    }
    return *this;
  }

  bool Db::isOpened() const throw(){
    return (this->db && this->db->dbh) ? true : false;
  }

  bool Db::ownsHandle() const throw(){
    return this->ownsDb;
  }

  fsl_db * Db::handle() throw(){
    return this->db;
  }
  
  fsl_db const * Db::handle() const throw() {
    return this->db;
  }

  Db & Db::begin(){
    this->assertOpened();
    this->assertRC( 
                   "begin()",
                    fsl_db_transaction_begin(*this) );
    return *this;
  }

  Db & Db::commit(){
    this->assertOpened();
    this->assertRC( "commit()",
                    fsl_db_transaction_end(*this, 0) );
    return *this;
  }

  Db & Db::rollback() throw() {
    this->assertOpened();
    fsl_db_transaction_end(*this, 1);
    return *this;
  }

  Db & Db::exec(std::string const & sql){
    return this->exec("%s", sql.c_str());
  }

  Db & Db::exec(char const * sql, ...){
    this->assertOpened();
    va_list vargs;
    int rc = 0;    
    va_start(vargs,sql);
    rc = fsl_db_execv( *this, sql, vargs );
    va_end(vargs);
    if(rc){
      this->propagateError();
      throw Exception(rc,"SQL execution failed for: %s", sql);
    }
    else return *this;
  }

  Db & Db::execMulti(char const * sql, ...){
    this->assertOpened();
    va_list vargs;
    int rc = 0;    
    va_start(vargs,sql);
    rc = fsl_db_exec_multiv( *this, sql, vargs );
    va_end(vargs);
    if(rc){
      this->propagateError();
      throw Exception(rc,"SQL multi-exec failed for: %s", sql);
    }
    else return *this;
  }

  Db & Db::execMulti(std::string const & sql){
    return this->execMulti("%s", sql.c_str());
  }


  Db & Db::attach(char const * filename, char const * label){
    this->assertRC( "attach()", fsl_db_attach(*this, filename, label) );
    return *this;
  }

  Db & Db::detach(char const * label){
    this->assertRC( "detach()", fsl_db_detach(*this, label) );
    return *this;
  }

  int Db::transactionLevel() const throw(){
    return this->db
      ? this->db->beginCount
      : 0;
  }

  Db::Transaction::Transaction(Db & db)
  : db(db), inTrans(false){
    db.begin();
    inTrans = db.transactionLevel();
  }

  Db::Transaction::~Transaction() throw(){
    if(inTrans) this->rollback();
  }

  void Db::Transaction::commit(){
    assert(inTrans);
    if(inTrans>0){
      inTrans = 0;
      db.commit();
    }
  }

  void Db::Transaction::rollback() throw(){
    assert(inTrans);
    if(inTrans){
#if 1
      inTrans = 0;
      db.rollback();
#else
      /* sane? */
      while(inTrans < db.transactionLevel()){
        db.rollback();
      }
#endif
    }
  }

  int Db::Transaction::level() const throw(){
    return db.transactionLevel();
  }

}// namespace fsl

#undef CERR
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<








































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted bindings/cpp/Deck.cpp.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp"
#include <cassert>

/* only for debugging */
#include <iostream>
#define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : "

namespace fsl {

  Deck::~Deck() throw(){
    delete this->deltaBase;
    if(this->ownsDeck){
      assert(this->d->allocStamp);
      fsl_deck_finalize(this->d);
    }
  }

  void Deck::setup(fsl_deck * mf, fsl_satype_e type){
    if(!mf){
      assert(!this->d);
      assert(this->ownsDeck);
      this->d = fsl_deck_malloc();
      if(!this->d) throw OOMException();
    }else{
      assert(this->d == mf);
      if(d && d->f && (d->f != this->cx.handle())){
        throw Exception(FSL_RC_MISUSE,
                        "Mis-matched fsl_cx contexts for deck.");
      }
    }
    if(ownsDeck){
      fsl_deck_init( this->cx, d, type );
    }
  }

  Deck::Deck(Context & cx, fsl_satype_e type)
    : cx(cx),
      d(NULL),
      deltaBase(NULL),
      ownsDeck(true){
    this->setup( NULL, type );
  }

  Deck::Deck(Context & cx, fsl_deck * d, bool ownsDeck)
    : cx(cx),
      d(d),
      deltaBase(NULL),
      ownsDeck(ownsDeck){
    if(!d){
      throw Exception(FSL_RC_MISUSE,
                      "A proxied Deck may not be NULL.");
    }
    this->setup( d, d->type );
  }

  void Deck::propagateError() const{
    fsl_error const * err = fsl_cx_err_get_e(this->cx);
    if(err->code){
      throw Exception(err);
    }
  }

  void Deck::assertRC(char const * context, int rc) const{
    if(rc){
      this->propagateError();
      throw Exception(rc, "%s: %s", context, fsl_rc_cstr(rc));
    }
  }

  Deck & Deck::cleanup() throw(){
    fsl_deck_clean(*this);
    return *this;
  }

  fsl_satype_e Deck::type() const throw(){
    return this->d->type;
  }

  fsl_id_t Deck::rid() const throw(){
    return this->d->rid;
  }

  fsl_uuid_cstr Deck::uuid() const throw(){
    return this->d->uuid;
  }

  Deck::operator fsl_deck *() throw(){
    return this->d;
  }

  Deck::operator fsl_deck const *() const throw(){
    return this->d;
  }

  Context & Deck::context() throw() { return this->cx; }
  Context const & Deck::context() const throw() { return this->cx; }

  fsl_deck * Deck::handle() throw(){
    return this->d;
  }

  fsl_deck const * Deck::handle() const throw(){
    return this->d;
  }

  bool Deck::hasAllRequiredCards() const throw(){
    return fsl_deck_has_required_cards(*this);
  }

  Deck const & Deck::assertHasRequiredCards() const{
    if(!fsl_deck_has_required_cards(*this)){
      throw Exception(fsl_cx_err_get_e(this->cx));
    }
    else return *this;
  }

  bool Deck::cardIsLegal(char cardLetter) const throw(){
    return fsl_card_is_legal( this->d->type, cardLetter );
  }

  Deck & Deck::unshuffle(bool calcRCard){
    int const rc = fsl_deck_unshuffle(*this, calcRCard);
    if(rc){
      this->propagateError();
      throw Exception(rc);
    }else return *this;
  }

  Deck const & Deck::output( fsl_output_f f, void * outState ) const{
    int const rc = fsl_deck_output( this->d, f, outState );
    if(rc) throw Exception(this->d->f->error);
    else return *this;
  }

  Deck const & Deck::output( std::ostream & os ) const{
    return this->output( fsl_output_f_std_ostream, &os );
  }

  Deck & Deck::save(bool isPrivate){
    int const rc = fsl_deck_save( *this, isPrivate );
    if(rc){
      this->propagateError();
      throw Exception(rc,"fsl_deck_save() failed: %s",
                      fsl_rc_cstr(rc));
    }
    else return *this;
  }

  Deck & Deck::load( fsl_id_t rid, fsl_satype_e type ){
    this->cleanup();
    int const rc = fsl_deck_load_rid( this->cx, *this,
                                      rid, type );
    if(rc){
      this->propagateError();
      throw Exception(rc, "fsl_deck_load_rid() failed "
                      "with %s for symbol: %" FSL_ID_T_PFMT,
                      fsl_rc_cstr(rc), (fsl_id_t)rid);
    }else return *this;
  }

  Deck & Deck::load( char const * symbolicName, fsl_satype_e type){
    this->cleanup();
    int const rc = fsl_deck_load_sym( this->cx, *this,
                                      symbolicName, type );
    if(rc){
      this->propagateError();
      throw Exception(rc, "fsl_deck_load_sym() failed "
                      "with %s for symbol: %s",
                      fsl_rc_cstr(rc), symbolicName);
    }else return *this;
  }

  Deck & Deck::load( std::string const & symbolicName, fsl_satype_e type){
    return this->load( symbolicName.c_str(), type );
  }

  Deck & Deck::setCardA( char const * name,
                         char const * tgt,
                         fsl_uuid_cstr uuid ){
    int const rc = fsl_deck_A_set(*this, name, tgt, uuid);
    this->assertRC("fsl_deck_A_set()", rc);
    return *this;
  }

  Deck & Deck::setCardB(fsl_uuid_cstr uuid){
    if(this->deltaBase){
      delete this->deltaBase;
      this->deltaBase = NULL;
    }
    int const rc = fsl_deck_B_set(*this, uuid);
    this->assertRC("fsl_deck_B_add()", rc);
    return *this;
  }


  Deck & Deck::setCardC( char const * comment ){
    int const rc = fsl_deck_C_set(*this, comment, -1);
    this->assertRC("fsl_deck_C_set()", rc);
    return *this;
  }

  Deck & Deck::setCardD(double julianDay){
    int const rc = fsl_deck_D_set(*this,
                                  julianDay<0
                                  ? fsl_julian_now()
                                  : julianDay);
    this->assertRC("fsl_deck_D_set()", rc);
    return *this;
  }

  Deck & Deck::setCardE( fsl_uuid_cstr uuid, double julian ){
    int const rc = fsl_deck_E_set(*this,
                                  julian<0
                                  ? fsl_julian_now()
                                  : julian,
                                  uuid );
    this->assertRC("fsl_deck_E_set()", rc);
    return *this;
  }


  Deck & Deck::addCardF(char const * name,
                        fsl_uuid_cstr uuid,
                        fsl_fileperm_e perm, 
                        char const * oldName ){
    int const rc = fsl_deck_F_add(*this, name, uuid, perm, oldName);
    this->assertRC("fsl_deck_F_add()", rc);
    return *this;
  }

  Deck & Deck::addCardJ( char isAppend, char const * key, char const * value ){
    int const rc = fsl_deck_J_add(*this, isAppend, key, value);
    this->assertRC("fsl_deck_J_add()", rc);
    return *this;
  }


  Deck & Deck::setCardK(fsl_uuid_cstr uuid){
    int const rc = fsl_deck_K_set(*this, uuid);
    this->assertRC("fsl_deck_K_set()", rc);
    return *this;
  }

  Deck & Deck::setCardL( char const * title ){
    int const rc = fsl_deck_L_set(*this, title, -1);
    this->assertRC("fsl_deck_L_set()", rc);
    return *this;
  }

  Deck & Deck::addCardM(fsl_uuid_cstr uuid){
    int const rc = fsl_deck_M_add(*this, uuid);
    this->assertRC("fsl_deck_M_add()", rc);
    return *this;
  }


  Deck & Deck::setCardN(char const * name){
    int const rc = fsl_deck_N_set(*this, name, -1);
    this->assertRC("fsl_deck_N_set()", rc);
    return *this;
  }

  Deck & Deck::addCardP(fsl_uuid_cstr uuid){
    int const rc = fsl_deck_P_add(*this, uuid);
    this->assertRC("fsl_deck_P_add()", rc);
    return *this;
  }

  Deck & Deck::addCardQ(char type, fsl_uuid_cstr target,
                        fsl_uuid_cstr baseline){
    int const rc = fsl_deck_Q_add(*this, type, target, baseline);
    this->assertRC("fsl_deck_Q_add()", rc);
    return *this;
  }

  Deck & Deck::addCardT(fsl_tagtype_e tagType,
                        char const * name,
                        fsl_uuid_cstr uuid,
                        char const * value){
    int const rc = fsl_deck_T_add( *this, tagType, uuid, name, value );
    this->assertRC("fsl_deck_T_add()", rc);
    return *this;
  }


  Deck & Deck::setCardU(char const * user){
    if(!user || !*user){
      user = fsl_cx_user_get(this->cx);
    }
    if(!user || !*user){
      throw Exception(FSL_RC_MISUSE,
                      "setCardU(): NULL/empty user name is not legal.");
    }
    int const rc = fsl_deck_U_set(*this, user);
    this->assertRC("fsl_deck_U_set()", rc);
    return *this;
  }

  Deck & Deck::setCardW(char const * content, fsl_int_t len){
    int const rc = fsl_deck_W_set(*this, content, len);
    this->assertRC("fsl_deck_W_set()", rc);
    return *this;
  }

  Deck::FCardIterator::~FCardIterator() throw()
  {}

  Deck::FCardIterator::FCardIterator(Deck & d, bool skipDeletedFiles)
    : d(&d),
      fc(NULL),
      skipDeleted(skipDeletedFiles)
  {
    int const rc = fsl_deck_F_rewind(d);
    if(rc) throw Exception(rc, "fsl_deck_F_rewind() failed: %s",
                           fsl_rc_cstr(rc));
    fsl_deck_F_next(d, &this->fc);
  }

  Deck::FCardIterator::FCardIterator() throw()
    : d(NULL),
      fc(NULL),
      skipDeleted(false)
  {}

  void Deck::FCardIterator::assertHasDeck(){
    if(!this->d) throw Exception(FSL_RC_MISUSE,
                                 "Iterator requires a deck object.");
  }

  Deck::FCardIterator & Deck::FCardIterator::operator++(){
    if(this->fc){
      do{
        int const rc = fsl_deck_F_next(*this->d, &this->fc);
        if(rc) throw Exception(rc);
      }while(skipDeleted && (this->fc && !this->fc->uuid));
    }
    return *this;
  }

  fsl_card_F const * Deck::FCardIterator::operator*(){
    return this->fc;
  }

  fsl_card_F const * Deck::FCardIterator::operator->(){
    if(!this->fc) throw Exception(FSL_RC_MISUSE,
                                  "Throwing to avoid "
                                  "dereferencing a NULL fsl_card_F.");
    return this->fc;
  }


  bool Deck::FCardIterator::operator==(FCardIterator const &rhs) const throw(){
    if(this->fc==rhs.fc) return true;
    else if(!this->fc || !rhs.fc) return false;
    return 0==fsl_strcmp(this->fc->name, rhs.fc->name);
  }
    
  bool Deck::FCardIterator::operator!=(FCardIterator const &rhs) const throw(){
    if(this->fc==rhs.fc) return false;
    else if(!this->fc || !rhs.fc) return true;
    return 0!=fsl_strcmp(this->fc->name, rhs.fc->name);
  }

  bool Deck::FCardIterator::operator<(FCardIterator const &rhs) const throw(){
    if(!this->fc) return rhs.fc ? true : false;
    else if(!rhs.fc) return false;
    return 0 > fsl_strcmp(this->fc->name, rhs.fc->name);
  }

  Deck * Deck::baseline(){
    if(FSL_SATYPE_CHECKIN==this->d->type){
      if(this->deltaBase) return this->deltaBase;
      else if(!this->d->B.uuid) return NULL;
      else if(!this->d->B.baseline){
        int const rc = fsl_deck_F_rewind(*this);
        this->assertRC("fsl_deck_rewind()", rc);
        assert(this->d->B.baseline);
      }
      return this->deltaBase = new Deck(this->cx,
                                        this->d->B.baseline,
                                        false);
    }else{
      return NULL;
    }
  }

  Deck::TCardIterator::TCardIterator(Deck & d)
  : ParentType(d.handle()->T)
  {}

  Deck::TCardIterator::TCardIterator()
  : ParentType()
  {}

  Deck::TCardIterator::~TCardIterator() throw()
  {}

  fsl_card_T const * Deck::TCardIterator::operator->() const{
    fsl_card_T const * rv = this->currentValue();
    if( !rv ) throw Exception(FSL_RC_MISUSE,
                              "Throwing to avoid dereferencing a NULL "
                              "fsl_card_T.");
    else return rv;
  }
  std::ostream & operator<<( std::ostream & os, Deck const & d ){
    d.output(os);
    return os;
  }

} // namespace fsl

#undef CERR
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




























































































































































































































































































































































































































































































































































































































































































































































































































































Deleted bindings/cpp/Exception.cpp.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp"
#include <cassert>

/* only for debugging */
#include <iostream>
#define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : "

namespace fsl {

  Exception::~Exception() throw() {
    fsl_error_clear(*this);
  }

  Exception::Exception(Exception const &other) throw()
    : err(fsl_error_empty){
    fsl_error_copy(&other.err, *this);
    //CERR << "Exception("<<err.code<<"): " << (char const *)err.msg.mem << '\n';
  }

  Exception & Exception::operator=(Exception const &other) throw(){
    if(&other != this){
      fsl_error_clear(*this);
      fsl_error_copy(&other.err, *this);
    }
    //CERR << "Exception("<<err.code<<"): " << (char const *)err.msg.mem << '\n';
    return *this;
  }

  Exception::Exception(int code, std::string const & msg) throw()
    : err(fsl_error_empty){
    fsl_error_set(*this, code, "%s",
                  msg.empty()
                  ? fsl_rc_cstr(code)
                  : msg.c_str());
    //CERR << "Exception("<<err.code<<"): " << (char const *)err.msg.mem << '\n';
    /* Reminder: we have to ignore any error here :/ */
  }

  Exception::Exception(int code) throw()
    : err(fsl_error_empty){
    fsl_error_set(*this, code, "%s", fsl_rc_cstr(code));
  }

  Exception::Exception() throw()
    : err(fsl_error_empty){
    fsl_error_set(*this, FSL_RC_ERROR, NULL);
  }

  Exception::Exception( fsl_error & err ) throw()
    : err(fsl_error_empty)
  {
    assert(err.code);
    fsl_error_move( &err, *this );
  }

  Exception::Exception( fsl_error const * err ) throw()
    : err(fsl_error_empty)
  {
    if(err && err->code){
      fsl_error_copy( err, *this );
    }else{
      fsl_error_set( *this, FSL_RC_MISUSE,
                     "Exception(fsl_error const *) ctor passed a %s!",
                     err ? "fsl_error with code==0" : "NULL");
    }
  }

  void Exception::error(int code, char const * fmt, va_list vargs) throw(){
    fsl_error_setv(*this, code, fmt, vargs);
    //CERR << "Exception("<<err.code<<"): " << (char const *)err.msg.mem << '\n';
  }

  Exception::Exception(int code, char const * fmt, ...) throw()
    : err(fsl_error_empty){
    va_list vargs;
    va_start(vargs,fmt);
    this->error(code, fmt, vargs);
    va_end(vargs);
  }

  Exception::Exception(int code, char const * fmt, va_list vargs) throw()
    : err(fsl_error_empty){
    this->error(code, fmt, vargs);
  }

  Exception::operator fsl_error * () throw(){
    return &this->err;
  }

  Exception::operator fsl_error const * () const throw(){
    return &this->err;
  }

  char const * Exception::messsage() const throw(){
    return this->what();
  }

  char const * Exception::what() const throw() {
    return (FSL_RC_OOM==this->err.code)
      ? fsl_rc_cstr(this->err.code)
      : fsl_buffer_cstr(&this->err.msg);
  }

  char const * Exception::codeString() const throw(){
    return fsl_rc_cstr(this->err.code);
  }

  int Exception::code() const throw(){
    return this->err.code;
  }

  OOMException::OOMException() throw()
    : Exception(FSL_RC_OOM)
  {
  }

} // namespace fsl

#undef CERR
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


















































































































































































































































Deleted bindings/cpp/Fossil.cpp.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp"

/**

 */
namespace fsl {


  Buffer::~Buffer() throw(){
    this->clear();
  }

  Buffer::Buffer(fsl_size_t startingSize)
    : buf(fsl_buffer_empty)
  {
    if(startingSize){
      if(fsl_buffer_reserve(&this->buf, startingSize)){
        throw OOMException();
      }
    }
  }

  Buffer::Buffer()
    : buf(fsl_buffer_empty)
  {}

  Buffer & Buffer::operator=(Buffer const & other){
    fsl_size_t const oldSize = this->buf.capacity;
    this->reset();
    if((&other != this)
       && !other.empty()
       && fsl_buffer_append(*this, other.mem(), other.used())
       ){
      throw OOMException();
    }
    /* A rather arbitrary heuristic to determine whether or not
       we should free up some spce by to resizing buf()
       after re-using it... */
    if((this->buf.capacity == oldSize)
       && (this->buf.capacity > 20)
       && (this->buf.used < (this->buf.capacity/2))){
      fsl_buffer_resize( *this, this->buf.used );
    }
    return *this;
  }

  Buffer::Buffer(Buffer const & other)
  : buf() {
    if(!other.empty()
       && fsl_buffer_append(*this, other.mem(), other.used())
       ){
      throw OOMException();
    }
  }

  Buffer::operator fsl_buffer *() throw(){
    return &this->buf;
  }

  Buffer::operator fsl_buffer const *() const throw(){
    return &this->buf;
  }

  fsl_buffer * Buffer::handle() throw(){
    return &this->buf;
  }
  
  fsl_buffer const * Buffer::handle() const throw(){
    return &this->buf;
  }

  bool Buffer::empty() const throw() { return 0==this->buf.used; }

  fsl_size_t Buffer::used() const throw() { return this->buf.used; }

  fsl_size_t Buffer::capacity() const throw() { return this->buf.capacity; }

  unsigned char const * Buffer::mem() const throw() {
    return this->buf.used
      ? this->buf.mem
      : NULL;
  }

  unsigned char * Buffer::mem() throw() {
    return this->buf.used
      ? this->buf.mem
      : NULL;
  }

  Buffer & Buffer::clear() throw() {
    fsl_buffer_clear(*this);
    return *this;
  }

  Buffer & Buffer::reset() throw() {
    fsl_buffer_reuse(*this);
    return *this;
  }

  Buffer & Buffer::reserve(fsl_size_t n){
    int const rc = fsl_buffer_reserve(*this, n);
    if(rc) throw Exception(rc, "Buffer::reserve() failed");
    return *this;
  }

  Buffer & Buffer::resize(fsl_size_t n){
    int const rc = fsl_buffer_resize(*this, n);
    if(rc) throw Exception(rc, "Buffer::resize() failed");
    return *this;
  }

  Buffer::iterator Buffer::begin() throw() {
    return this->buf.used
      ? this->buf.mem
      : NULL;
  }

  Buffer::iterator Buffer::end() throw() {
    return this->buf.used
      ? this->buf.mem + this->buf.used
      : NULL;
  }

  Buffer::const_iterator Buffer::begin() const throw() {
    return this->buf.mem;
  }

  Buffer::const_iterator Buffer::end() const throw() {
    return this->buf.used
      ? this->buf.mem + this->buf.used
      : NULL;
  }

  std::ostream & operator<<( std::ostream & os, Buffer const & b ){
    fsl_size_t n = 0;
    char const * s = fsl_buffer_cstr2(b, &n);
    if(n) os.write(s, n);
    return os;
  }

  Buffer & Buffer::appendf(char const * fmt, ...){
    va_list vargs;
    int rc = 0;
    va_start(vargs,fmt);
    rc = fsl_buffer_appendfv(*this, fmt, vargs);
    va_end(vargs);
    if(rc){
      throw Exception(rc, "fsl_appendfv() failed: %s",
                      fsl_rc_cstr(rc));
    }
    return *this;
  }

  char const * Buffer::c_str() const throw(){
    return fsl_buffer_cstr(*this);
  }

  void Buffer::toss(int errorCode) const{
    throw Exception(errorCode, "%s", fsl_buffer_cstr(*this));
  }

  int fsl_input_f_std_istream( void * state, void * dest, fsl_size_t * n ){
    std::istream * is = static_cast<std::istream *>(state);
    try{
      /**
         We have to read byte-by-byte to fullfil the requirement that
         we write the number of read bytes to *n. std::istream::read()
         does not give us a (direct) way to know exactly how many
         bytes were read before EOF.
      */
      int rc;
      fsl_size_t i;
      unsigned char * out = (unsigned char *) dest;
      for( i = 0; i < *n; ++i ){
        rc = is->get();
        if(is->eof()){
          *n = i;
          return 0;
        }else if(!is->good()){
          return FSL_RC_IO;
        }else{
          *out++ = rc & 0xFF;
        }
      }
      return 0;
    }catch(...){
      return FSL_RC_IO;
    }
  }

} // namespace fsl
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


































































































































































































































































































































































































Deleted bindings/cpp/Makefile.in.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
all:
include ../../subdir-inc.make
#$(error $(TOP_SRCDIR))
#CPPFLAGS += -I$(TOP_INCDIR)
#CPPFLAGS += -I$(TOP_SRCDIR)/include
CPPFLAGS += -I$(TOP_SRCDIR)/src# workaround for in-tree sqlite3.h
CXXFLAGS += -fPIC -Wall -Werror

#INCLUDES_PATH ?= $(HOME)/include /usr/local/include /usr/include

LIBFOSSIL.LDFLAGS := -L$(TOP_SRCDIR) -lfossil -lz

#ifeq (,$(strip $(filter distclean clean,$(MAKECMDGOALS))))
#$(LIBFOSSIL.LDFLAGS):
#	$(MAKE) -C ..
#endif

CPP.SRC := 	\
	Context.cpp \
	Db.cpp \
	Deck.cpp \
	Exception.cpp \
	Fossil.cpp \
	OStream.cpp

OBJECTS := $(patsubst %.cpp,%.o,$(CPP.SRC))

libfossil++.DLL.OBJECTS := $(OBJECTS)
libfossil++.BIN.LDFLAGS := -lstdc++ -fPIC $(LIBFOSSIL.LDFLAGS)# -lsqlite3 -lz
libfossil++.LIB.OBJECTS := $(libfossil++.DLL.OBJECTS)
Makefile: Makefile.in
$(libfossil++.DLL.OBJECTS): Makefile
########################################################################
# Shared lib
$(eval $(call ShakeNMake.CALL.RULES.DLLS,libfossil++))
all: $(libfossil++.DLL)
$(libfossil++.DLL): $(libfossil++.DLL.OBJECTS)
#Static lib (don't work well with _some_ C++ template uses)
#$(eval $(call ShakeNMake.CALL.RULES.LIBS,libfossil++))
#all: $(libfossil++.LIB)
#$(libfossil++.LIB): $(libfossil++.LIB.OBJECTS)

test.BIN.OBJECTS := test.o
ifeq (0,1)
test.BIN.LDFLAGS := $(LIBFOSSIL.LDFLAGS) $(EXTRA_LIBS) -lstdc++ -L. -lfossil++ @SH_LINKFLAGS@
else
test.BIN.OBJECTS += $(libfossil++.DLL.OBJECTS)
test.BIN.LDFLAGS := $(LIBFOSSIL.LDFLAGS) $(EXTRA_LIBS) -lstdc++ @SH_LINKFLAGS@
endif
$(eval $(call ShakeNMake.CALL.RULES.BINS,test))
all: $(test.BIN)


########################################################################
# A quick-n-dirty amalgamation build...
INCD := $(TOP_INCDIR)/fossil-scm
AMAL_D := $(TOP_SRCDIR_REL)
AMAL_C := $(AMAL_D)/libfossil.c
AMAL_H := $(AMAL_D)/libfossil.h
AMAL_CPP := $(AMAL_D)/libfossil++.cpp
AMAL_HPP := $(AMAL_D)/libfossil++.hpp
AMAL_CONF.H := $(AMAL_D)/libfossil-config.h
CLEAN_FILES += $(AMAL_HPP) $(AMAL_CPP)

$(AMAL_C) $(AMAL_H):
	$(MAKE) -C $(TOP_SRCDIR_REL)/src amal

AMAL_CPP.SRC := \
	$(CPP.SRC)

# ACHTUNG: in AMAL_H.SRC, $(AMAL_CONF.H) MUST be included first.
AMAL_HPP.SRC := \
	$(INCD)/fossil.hpp

$(AMAL_HPP.SRC):
$(AMAL_CPP.SRC):
$(AMAL_HPP): Makefile $(AMAL_HPP.SRC)
$(AMAL_CPP): Makefile $(AMAL_HPP) $(AMAL_CPP.SRC)
$(AMAL_CONF.H): $(MAIN_MAKEFILES)
	@echo "Generating $@ ..."
	cd $(TOP_SRCDIR_REL) && sh configure --amal

$(AMAL_HPP):
	@echo "Creating $@..."
	@{ \
		echo '#if !defined(FSLPP_AMALGAMATION_BUILD)'; \
		echo '#define FSLPP_AMALGAMATION_BUILD 1'; \
		echo '#endif'; \
		echo '#include "$(notdir $(AMAL_H))"'; \
	} > $@
	@{ \
		for i in $(AMAL_HPP.SRC); do \
			echo "/* start of file $$i */"; \
			cat $$i; \
			echo "/* end of file $$i */"; \
		done; \
	} | sed  \
		 -e '/[ ]*#[ ]*include[ ]*.*fossil.*\.h[>"]/d' \
		 -e '/[ ]*#[ ]*include[ ]*.*".*config\.h[>"]/d' \
		>> $@

$(AMAL_CPP): $(AMAL_HPP)
	@echo "Creating $@..."
	@echo '#include "$(notdir $(AMAL_HPP))"' > $@
	@{ \
		for i in $(AMAL_CPP.SRC); do \
			echo "/* start of file $$i */"; \
			cat $$i; \
			echo "/* end of file $$i */"; \
		done; \
	} | sed \
		 -e '/[ ]*#[ ]*include[ ]*.*fossil.*\.h.*[>"]/d' \
		>> $@

AMAL_CPP_FLAGS := -I$(TOP_INCDIR) -I$(TOP_SRCDIR_REL) -I. -I$(TOP_SRCDIR_REL)/src# for sqlite3.h :/
.PHONY: amal
amal: $(AMAL_CPP)
	@ls -ls $(AMAL_CPP) $(AMAL_HPP)
	@if which g++ >/dev/null; then \
		echo "Trying GCC..."; \
		g++ -c $(AMAL_CPP) -pedantic -Wstrict-aliasing -Wall -Werror -Wno-long-long $(AMAL_CPP_FLAGS); \
	fi; true
	@if which clang >/dev/null; then \
		echo "Trying clang..."; \
		clang -c $(AMAL_CPP) -pedantic -Wstrict-aliasing -Wall -Werror -Wno-long-long $(AMAL_CPP_FLAGS); \
	fi; true

# /amalgamation
########################################################################
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


































































































































































































































































Deleted bindings/cpp/OStream.cpp.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp"

/**

 */
namespace fsl {


  fsl_int_t fsl_appendf_f_std_ostream( void * state,
                                       char const * data,
                                       fsl_int_t n ){
    std::ostream * os = static_cast<std::ostream *>(state);
    try{
      os->write( data, (std::streamsize)n );
      return (*os) ? 0 : FSL_RC_IO;
    }catch(...){
      return FSL_RC_IO;
    }
  }

  int fsl_output_f_std_ostream( void * state,
                                void const * data,
                                fsl_size_t n ){
    std::ostream * os = static_cast<std::ostream *>(state);
    try{
      os->write( (char const *)data, (std::streamsize)n );
      return (*os) ? 0 : FSL_RC_IO;
    }catch(...){
      return FSL_RC_IO;
    }
  }


  ContextOStreamBuf::~ContextOStreamBuf() throw(){
    this->sync();
    if( this->m_os ){
      this->m_os->rdbuf( this->m_old );
    }
  }

  void ContextOStreamBuf::setup( fsl_cx * fx ){
    if(!fx) throw Exception(FSL_RC_MISUSE,
                            "ContextOStreamBuf requires "
                            "a non-NULL fsl_cx.");
    this->f = fx;
    this->setp( 0, 0 );
    this->setg( 0, 0, 0 );
    if(m_os) m_os->rdbuf( this );
  }

  ContextOStreamBuf::ContextOStreamBuf( fsl_cx * f, std::ostream & os )
    : f(NULL),
      m_os(&os),
      m_old(os.rdbuf()){
    this->setup(f);
  }

  ContextOStreamBuf::ContextOStreamBuf( fsl_cx * f )
    : f(NULL),
      m_os(NULL),
      m_old(NULL){
    this->setup(f);
  }

  ContextOStreamBuf::ContextOStreamBuf( Context & cx, std::ostream & os )
    : f(NULL),
      m_os(&os),
      m_old(os.rdbuf()){
    this->setup(cx.handle());
  }

  ContextOStreamBuf::ContextOStreamBuf( Context & cx )
    : f(NULL),
      m_os(NULL),
      m_old(NULL){
    this->setup(cx.handle());
  }


  int ContextOStreamBuf::overflow( int c ){
    char const ch = c & 0xFF;
    int const rc = fsl_output(this->f, &ch, 1);
    if(rc) throw Exception(FSL_RC_IO,
                           "fsl_output() failed with code %d (%s)",
                           rc, fsl_rc_cstr(rc));
    else return 0;
  }

  int ContextOStreamBuf::sync(){
    return this->f
      ? fsl_flush(this->f)
      : FSL_RC_IO;
  }

  ContextOStream::~ContextOStream() throw(){
    delete this->sb;
  }

  ContextOStream::ContextOStream( fsl_cx * f )
    :f(f),
     sb(new ContextOStreamBuf(f, *this)){
  }

  ContextOStream::ContextOStream( Context & cx )
    :f(cx.handle()),
     sb(new ContextOStreamBuf(f, *this)){
  }

  ContextOStream & ContextOStream::appendf(char const * fmt, ...){
    va_list vargs;
    int rc = 0;
    va_start(vargs,fmt);
    rc = fsl_outputfv(this->f, fmt, vargs);
    va_end(vargs);
    if(rc){
      throw Exception(rc, "fsl_outputfv() failed: %s",
                      fsl_rc_cstr(rc));
    }
    return *this;
  }

  FslOutputFStreamBuf::~FslOutputFStreamBuf() throw(){
    if( this->m_os ){
      this->m_os->rdbuf( this->m_old );
    }
  }

  void FslOutputFStreamBuf::setup( fsl_output_f f, void * state ){
    if(!f) throw Exception(FSL_RC_MISUSE,
                           "FslOutputFStream output function may "
                           "not be NULL.");
    this->out = f;
    this->outState = state;
    this->setp( 0, 0 );
    this->setg( 0, 0, 0 );
    if(m_os) m_os->rdbuf( this );
  }

  FslOutputFStreamBuf::FslOutputFStreamBuf( fsl_output_f f, void * state )
    : out(NULL), outState(NULL),
      m_os(NULL), m_old(NULL)
  {
    this->setup(f, state);
  }

  FslOutputFStreamBuf::FslOutputFStreamBuf( fsl_output_f f, void * state,
                                            std::ostream & os)
    : out(NULL), outState(NULL),
      m_os(&os), m_old(os.rdbuf())
  {
    this->setup(f, state);
  }

  int FslOutputFStreamBuf::overflow( int c ){
    char const ch = c & 0xFF;
    int const rc = this->out( this->outState, &ch, 1);
    if(rc) throw Exception(FSL_RC_IO,
                           "fsl_output_f() proxy failed with code "
                           "%d (%s)", rc, fsl_rc_cstr(rc));
    else return 0;
  }

  int FslOutputFStreamBuf::sync(){
    return 0;
  }

  FslOutputFStream::~FslOutputFStream() throw(){
    delete this->sb;
  }

  FslOutputFStream::FslOutputFStream( fsl_output_f out, void * outState )
    : sb(new FslOutputFStreamBuf(out, outState, *this)){
  }

  fsl_int_t FslOutputFStream::fslAppendfF( void * state,
                                           char const * s, fsl_int_t n ){
    FslOutputFStream * out = static_cast<FslOutputFStream*>(state);
    if(out->good()){
      out->write( s, (std::streamsize)n );
      return out->good() ? 0 : -1;
    }else return -1;
  }

  FslOutputFStream & FslOutputFStream::appendf(char const * fmt, ...){
    va_list vargs;
    fsl_int_t rc = 0;
    va_start(vargs,fmt);
    rc = fsl_appendfv(FslOutputFStream::fslAppendfF, this, fmt, vargs);
    va_end(vargs);
    if(rc<0) throw Exception(FSL_RC_IO,
                             "fsl_appendfv() failed mysteriously.");
    return *this;
  }

  BufferOStream::BufferOStream(fsl_buffer * b) :
    FslOutputFStream(fsl_output_f_buffer, b){
    if(!b) throw Exception(FSL_RC_MISUSE,
                           "fsl_buffer argument may not be NULL.");
  }

  BufferOStream::~BufferOStream() throw(){
  }

} // namespace fsl
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




























































































































































































































































































































































































































Deleted bindings/cpp/test.cpp.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp" /* MUST come first b/c of config bits. */
#include <cassert>
#include <iostream>
#include <fstream>
#include <list>
#include <cstdlib> /* EXIT_SUCCESS, EXIT_FAILURE */

#define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : "
#define COUT std::cout << __FILE__ << ":" << std::dec << __LINE__ << " : "

#define RCStr(C) fsl_rc_cstr(C)

/**
   A functor for use with fsl::Stmt::eachRow().
*/
struct RowDumper {
  std::ostream & os;
  char const * separator;
  bool showHeader;
  explicit RowDumper(std::ostream & os = std::cout)
    : os(os),
      separator("\t"),
      showHeader(true)
  {}
  void operator()(fsl::Stmt &s) const{
    int i;
    int const n = s.columnCount();
    if(this->showHeader && (1==s.stepCount())){
      for(i = 0; i < n; ++i ){
        this->os << (i ? this->separator : "") << s.columnName(i);
      }
      this->os << '\n';
    }
    for(i = 0; i < n; ++i ){
      this->os << (i ? this->separator : "") << s.getText(i);
    }
    this->os << '\n';
  }
};

static void test_db_1(){
  namespace f = fsl;
  char const * dbName = 0
    ? "/fail"
    : ":memory:"
    ;
  //using fsl::Exception;
  //throw Exception(FSL_RC_NYI, "Just %s", "testing");
  f::Db db;
  f::Stmt st(db);
  db.open(dbName, FSL_OPEN_F_RWC);
  COUT << "Opened db: "<<db.filename()<<'\n';
  db.begin();
  st.prepare("CREATE TABLE t(a INTEGER,b TEXT)").stepExpectDone();
  
  st.finalize().prepare("INSERT INTO t(a,b) VALUES(?1,?2)");
  // Try out the StmtBinder for insertions...
  f::StmtBinder b(st);
  b
    (1)("once").insert()
    (2)("twice").insert()
    (3.3)("Quite possibly thrice-point-thrice")
    .insert();

  int threw = 0;
  try{
    b(7)(8)
      ("must throw - too many bind() values");
  }catch(f::Exception const & ex){
    threw = ex.code();
  }
  assert(FSL_RC_RANGE==threw);

  // Bind a std::list of parameters... (not terribly useful, probably)
  typedef std::list<std::string> LI;
  LI li;
  li.push_back("4");
  li.push_back("fource. Or fice.");
  b.bindList(li).insert();

  st.finalize()
    .prepare("SELECT rowid, a, b FROM t ORDER BY a")
    .eachRow( RowDumper() )
    .finalize();

  f::Buffer sb;
  (sb << "SELECT ").appendf("%Q", "percent 'Q'");
  st.prepare(sb)
    .eachRow( RowDumper() );

}

static void test_stream_1(fsl::Context & cx){
  namespace f = fsl;

  f::ContextOStream fout(cx);
  {
    char const * sym = "rid:1";
    fout << "UUID of ["<<sym<<"]: " << cx.symToUuid(sym)<<'\n';
  }

  {
    fsl_uuid_cstr uuid = NULL;
    fsl_id_t rid = 0;
    fsl_ckout_version_info(cx, &rid, &uuid);
    fout << "Checkout version: "<<rid << " ==> "<<uuid<<'\n';
  }

  char const * filename = 1
    ? __FILE__
    : "/fail"
    ;
  std::ifstream is(filename);
  if(!is.good()){
    throw f::Exception(FSL_RC_IO,"Cannot open input file: %s",
                       filename);
  }
  f::Buffer buf;
  int rc = fsl_buffer_fill_from( buf,
                                 f::fsl_input_f_std_istream,
                                 &is );
  if(rc){
    throw f::Exception(rc,"Error (%d) %s reading from file: %s",
                       rc, fsl_rc_cstr(rc), filename);
  }
  assert(buf.used());
  COUT << "Read "<< buf.used()
       <<" bytes from "<<filename
       <<" via std::istream proxy.\n";
  buf.reset();
  assert(0==buf.used());
  assert(0<buf.capacity());
  buf << "Hi, world! ";
  buf.appendf("%Q", "sql 'escaped'") << '\n';
  COUT << "Buffer contents: " << buf;

  buf.reset();
  assert(0==buf.used());
  assert(0<buf.capacity());
  f::FslOutputFStream ops(fsl_output_f_buffer, buf.handle());
  ops << "Hi, world!";
  assert(10 == buf.used());

  buf.clear();
  assert(0==buf.used());
  assert(0==buf.capacity());
  assert(NULL==buf.mem());
}

static void test_deck_1(fsl::Context &cx){
  namespace f = fsl;
  int threw = 0;
  f::Deck d(cx, FSL_SATYPE_CONTROL);
  f::ContextOStream fout(cx);

  try {
    d.assertHasRequiredCards();
  }catch(f::Exception const &ex){
    threw = ex.code();
    fout << "Got expected exception: " << ex.what() << '\n';
  }
  assert(FSL_RC_SYNTAX==threw);

  {
    f::Context::Transaction tr(cx)
      /* We'll roll back everything done here... */
      ;
    const std::string tgtArty(cx.symToUuid("rid:1"));
      /* self-referencing tag in a FSL_SATYPE_CONTROL is not legal, so
         we arbitrarily choose the initial empty checking. */;
    fout << "Custom "<< fsl_satype_cstr(d.type()) << " deck:\n";
    d.setCardD()
      .setCardU()
      .addCardT(FSL_TAGTYPE_ADD, "myTag", tgtArty.c_str(), "its value")
      .addCardT(FSL_TAGTYPE_PROPAGATING, "myOtherTag", tgtArty.c_str())
      .addCardT(FSL_TAGTYPE_CANCEL, "sumdumtag", tgtArty.c_str())
      //.unshuffle().output(fout)
      ;
    d.save();
    fout << "RID/UUID after save (will be rolled back): "<<d.rid()
         << ' ' << d.uuid()<<'\n';

    {

      typedef f::Deck::TCardIterator TagIter;
      TagIter it(d);
      TagIter end;
      fout << "Iterating over tags:";
      for( ; it != end; ++it ){
        fsl_card_T const * tag = *it;
        fout << ' ' << fsl_tag_prefix_char(tag->type)
             << tag->name;
      }
      fout << '\n';
    }

    fout << "Re-read artifact via Deck::load():\n";
    fout << f::Deck(cx).load( d.rid() );

    f::Buffer content;
    cx.getContent( d.rid(), content );
    fout << "And again via Context::getContent():\n" << content;
  }

  if(0){
    fsl_id_t rid = 1;
    fout << "Loading checkin #"<<rid<<"...\n";
    d.load( rid ).output( fout );
  }

  { /* FCardIterator... */
    f::Deck withF(cx);
    withF.load("current");
    f::Deck::FCardIterator fit(withF);
    f::Deck::FCardIterator end;
    fout << "Iterating over F-cards...\n";
    assert(*fit);
    assert(!*end);
    assert(fit!=end);
    int counter = 0;
    for( ; fit != end; ++fit, ++counter ){
      fsl_card_F const * fc = *fit;
      assert(fc);
      assert(fc->name);
      assert(fit->name == fc->name);
    }
    fout << "Traversed "
         <<counter<<" F-card(s) in deck #"
         <<withF.rid()<<".\n";

  }

}

int main(int argc, char const * const * argv ){
  int rc = EXIT_SUCCESS;
  try {
    namespace f = fsl;
    f::Context cx;
    f::ContextOStream fout(cx);
    fout << "ContextOStream...\n";
    f::ContextOStreamBuf cout2(cx, std::cerr);
    f::ContextOStreamBuf cerr2(cx, std::cout);
    CERR << "cerr through fsl_output()\n";
    COUT << "cout through fsl_output()\n";
    cx.openCheckout();

    test_db_1();
    test_stream_1(cx);
    test_deck_1(cx);


    /* Manually closing the Context is not generally required,
       we're just testing an assertion or three... */
    assert(cx.dbRepo().isOpened());
    assert(cx.dbCheckout().isOpened());
    cx.closeDbs();
    assert(!cx.dbRepo().isOpened());
    assert(!cx.dbCheckout().isOpened());
  }catch(fsl::Exception const &fex){
    CERR << "EXCEPTION: " << RCStr(fex.code()) << ": " << fex.what() << '\n';
    rc = EXIT_FAILURE;
  }catch(std::exception const &ex){
    CERR << "EXCEPTION: " << ex.what() << '\n';
    rc = EXIT_FAILURE;
  }
  COUT << "Exiting. Result = " << (rc ? ":`(" : ":-D") << '\n';
  return rc;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






























































































































































































































































































































































































































































































































































Deleted bindings/s2/Makefile.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
all:
include ../../subdir-inc.make

CPPFLAGS += -I$(TOP_SRCDIR)/src# workaround for in-tree sqlite3.h
CPPFLAGS += -fPIC
CPPFLAGS+=-DCWAL_ENABLE_TRACE=0

LIBFOSSIL.LDFLAGS := -L$(TOP_SRCDIR) -lfossil
ifeq (1,1)
  # In static build, we need some extra flags...
  LIBFOSSIL.LDFLAGS += $(LDFLAGS_MODULE_LOADER) -lpthread -lz
endif

ifeq (,$(strip $(filter distclean clean,$(MAKECMDGOALS))))
$(LIBFOSSIL.LDFLAGS):
	$(MAKE) -C ..
endif

S2_CLIENT_LDFLAGS := $(LIBFOSSIL.LDFLAGS) $(LDFLAGS_MODULE_LOADER)

########################################################################
# INC_SEARCH: $(call)able function. $(1) should be the the name of
# a C header file to search for under $(INCLUDES_PATH).
INCLUDES_PATH ?= $(HOME)/include /usr/local/include /usr/include
define INC_SEARCH
$(call ShakeNMake.CALL.FIND_FILE,$(1),$(INCLUDES_PATH))
endef

#$(error ltdl=$(HAVE_LT_DLOPEN) dl=$(HAVE_DLOPEN) mods=$(ENABLE_MODULES))
########################################################################
# Bins and libs...

########################################################################
# Check for readline
ENABLE_READLINE := $(FSL_ENABLE_READLINE)
ifeq (1,$(ENABLE_READLINE))
  READLINE_H := $(call INC_SEARCH,readline/readline.h)
  ifneq (,$(READLINE_H))
#    READLINE_IMPL := readline
    $(info Enabling GNU Readline)
    LDFLAGS_READLINE := -lreadline
  else
    $(info Not enabling GNU Readline)
    ENABLE_READLINE := 0
  endif
endif
# /readline
########################################################################

########################################
# Linenoise (terminal input lib)...
LN_OBJ :=
ENABLE_LINENOISE := 1
ifeq (1,$(ENABLE_READLINE))
  $(info Not enabling linenoise)
  ENABLE_LINENOISE := 0
else
  LN_DIR := linenoise
  ifneq (,$(wildcard $(LN_DIR)/*.c))
    ENABLE_LINENOISE := 1
    $(info Enabling linenoise)
  else
    $(info Not enabling linenoise)
    ENABLE_LINENOISE := 0
  endif
  ifeq (1,$(ENABLE_LINENOISE))
    LN_OBJ := $(LN_DIR)/linenoise.o $(LN_DIR)/utf8.o
    CLEAN_FILES += $(LN_OBJ)
    $(LN_DIR)/utf8.o: CPPFLAGS+=-std=c99 -DUSE_UTF8=1
    $(LN_DIR)/linenoise.o: CPPFLAGS+=-std=c99 -DUSE_UTF8=1 -D_BSD_SOURCE
  else
    LN_OBJ :=
  endif
endif
# /linenoise
########################################

########################################
# s2sh a.k.a. f-s2sh
shell2.o: CPPFLAGS+=-DS2_SHELL_EXTEND
shell2.o: CPPFLAGS+=-DS2SH_FOR_UNIT_TESTS=1
cliapp.o: CPPFLAGS+=-DCLIAPP_ENABLE_LINENOISE=$(ENABLE_LINENOISE)
cliapp.o: CPPFLAGS+=-DCLIAPP_ENABLE_READLINE=$(ENABLE_READLINE)
shell2.o shell_extend.o: CPPFLAGS+=-DS2_AMALGAMATION_BUILD


f-s2sh.BIN.OBJECTS := shell2.o shell_extend.o cliapp.o $(LN_OBJ) s2_amalgamation.o
f-s2sh.BIN.LDFLAGS := $(S2_CLIENT_LDFLAGS) $(LDFLAGS_READLINE)
$(eval $(call ShakeNMake.CALL.RULES.BINS,f-s2sh))
all: $(f-s2sh.BIN)
# s2's inclusion of miniz breaks its policy of building with
# the (-pedantic -std=c89) flags, and requires that we be less
# stringent with compilation flags :(...
s2_amalgamation.o: CFLAGS=-Wall -Werror -Wsign-compare -g -UNDEBUG -DDEBUG=1
s2_amalgamation.o shell2.o shell_extend.o: CPPFLAGS+=-DS2_ENABLE_ZLIB=1 -DHAVE_CONFIG_H
s2_amalgamation.o: config.h

# end bins and libs
########################################################################

########################################
# Set up file-specific CPPFLAGS/LDFLAGS.
# basic module setup
ifeq (1,$(ENABLE_MODULES))
  s2_amalgamation.o: CPPFLAGS+=-DS2_ENABLE_MODULES=1
  f-s2sh.BIN.LDFLAGS += $(LDFLAGS_MODULE_LOADER)
  ifeq (1,$(HAVE_LIBDL)) # use libdl
    s2_amalgamation.o: CPPFLAGS+=-DS2_HAVE_DLOPEN=1
  else # use libltdl
    s2_amalgamation.o: CPPFLAGS+=-DS2_HAVE_LTDLOPEN=1
  endif
endif
# end modules
########################################################################

########################################################################
# Unit test stuff...
UNIT_SCRIPT_LIST := $(sort $(subst ./,,$(wildcard unit/???-???-*.s2 unit2/???-*.s2)))
$(UNIT_SCRIPT_LIST):
S2SH.SHELL.FLAGS ?= --no-init-script
# -w
UNIT_RUN_CMD = ./$(f-s2sh.BIN) $(S2SH.SHELL.FLAGS)
UNIT_MEGA.S2 := UNIT.s2
UNIT_MEGA2.S2 := UNIT-import.s2
UNIT_GENERATED := $(UNIT_MEGA.S2) $(UNIT_MEGA2.S2)
CLEAN_FILES += $(UNIT_GENERATED)
$(UNIT_MEGA.S2): $(UNIT_SCRIPT_LIST) Makefile
	@echo "Generating $@..."
	@{ \
		false && echo "const INTERN_THESE=['object','array','integer','double','string','function','bool'];"; \
		for i in $(UNIT_SCRIPT_LIST); do \
			echo "scope {/* begin file: $$i */"; \
			cat $$i; \
			echo "/* end file: $$i */;;}"; \
			echo ""; \
		done; \
	} > $@
$(UNIT_MEGA2.S2): $(UNIT_SCRIPT_LIST) Makefile
	@echo "Generating $@..."
	@{ \
		false && echo "const INTERN_THESE=['object','array','integer','double','string','function','bool'];"; \
		for i in $(UNIT_SCRIPT_LIST); do \
			echo "import(false,'$$i');"; \
		done; \
	} > $@

.PHONY: unit unit-proxy unit2
unit-proxy: $(f-s2sh.BIN) $(UNIT_GENERATED)
	@for i in $(UNIT_SCRIPTS_ALL); do \
		cmd="$(UNIT_RUN_CMD) -f $$i"; \
		echo "****************************** Script [$$i]"; \
		echo $$cmd; $$cmd || exit $$?; \
		echo "****************************** Done [$$i]"; \
	done
	@echo "Done running through unit test scripts."

unit: UNIT_SCRIPTS_ALL:=$(UNIT_SCRIPT_LIST) $(UNIT_GENERATED)
unit: unit-proxy
unit2: UNIT_SCRIPTS_ALL:=$(filter unit2/%,$(UNIT_SCRIPT_LIST))
unit2: unit-proxy
.PHONY: unit-r
.PHONY: unit-rc
unit-r: S2SH.SHELL.FLAGS:=--no-init-script -norv -norc
unit-r: unit
unit-s: S2SH.SHELL.FLAGS:=--no-init-script -rc -rc -nosi
unit-s: unit
unit-rc: S2SH.SHELL.FLAGS:=--no-init-script -norv -norc
unit-rc: unit
unit-rsc: S2SH.SHELL.FLAGS:=--no-init-script -norv -norc -nosi
unit-rsc: unit
units:
	@for i in unit unit-r unit-rc unit-s unit-rsc; do \
		echo "Making $$i ..."; \
		$(MAKE) $$i || exit $$?; \
	done
include vg.make
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
































































































































































































































































































































































Deleted bindings/s2/cliapp.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif
#include "cliapp.h"
#include <string.h>
#include <assert.h>
#include <stdio.h> /* vsprintf() */
#include <stdlib.h> /* abort() */

#ifndef CLIAPP_ENABLE_READLINE
#  define CLIAPP_ENABLE_READLINE 0
#endif

#ifndef CLIAPP_ENABLE_LINENOISE
#  define CLIAPP_ENABLE_LINENOISE 0
#endif

#if CLIAPP_ENABLE_READLINE && CLIAPP_ENABLE_LINENOISE
#  error "Use *one* of CLIAPP_ENABLE_READLINE or CLIAPP_ENABLE_LINENOISE, not both."
#endif

#if CLIAPP_ENABLE_LINENOISE
#  include "linenoise/linenoise.h"
#elif CLIAPP_ENABLE_READLINE
#  include "readline/readline.h"
#  include "readline/history.h"
#endif


#if 1
#define MARKER(pfexp) if(1) printf("%s:%d:\t",__FILE__,__LINE__); \
  if(1) printf pfexp
#else
static void noop_printf(char const * fmt, ...) {}
#define MARKER(pfexp) if(0) noop_printf pfexp
#endif
#define MESSAGE(pfexp) printf pfexp

struct CliApp cliApp = {
0/*argc*/, 0/*argv*/,
0/*cursorNonflag*/,
0/*errMsg*/,
0/*flags*/,
vprintf/*print*/,
0/*argCallback*/,
{/*doubleDash*/
  0/*argc*/,
  0/*argv*/
},
{/*lineread*/
  CLIAPP_ENABLE_LINENOISE ? 1 : (CLIAPP_ENABLE_READLINE ? 2 : 0) /*enabled*/,
  0 /* historyFile */, 0 /*needsSave*/
}
};

enum {
/** Buffer size for cliapp_errmsg() */
CLIAPP_MSGBUF_SIZE = 1024 * 4,
/** Buffer size for cliapp_process_argv() argument keys. */
CLIAPP_ARGV_KBUF_SIZE = 1024 * 2,
/** Maximum cliapp_process_argv() arg count. */
CLIAPP_ARGV_COUNT = (int)(1024 * 2 / sizeof(CliAppArg))
};

static char cliAppErrBuf[CLIAPP_MSGBUF_SIZE] = {0};

static char const * cliapp_verrmsg(char const * fmt, va_list args){
  int rc;
  char * buf = cliAppErrBuf;
  rc = vsprintf(buf, fmt, args)
    /* Noting that vsnprintf() requires C99 and s2 aims to compile in
       strict C89 mode. */;
  if(rc >= CLIAPP_MSGBUF_SIZE){
    fprintf(stderr,"%s:%d: Internal misuse of error message buffer. "
            "Dangerous buffer overrun!\n",
            __FILE__, __LINE__);
    abort();
  }
  return cliApp.errMsg = buf;
}
static char const * cliapp_errmsg(char const * fmt, ...){
  /** Stores error messages, at least until it's overwritten. */
  char const * rc;
  va_list args;
  va_start(args,fmt);
  rc = cliapp_verrmsg(fmt, args);
  va_end(args);
  return rc;
}

void cliapp_err_clear(){
  cliApp.errMsg = 0;
  cliAppErrBuf[0] = 0;
}

char const * cliapp_err_get(){
  return cliApp.errMsg;
}

static const CliAppSwitch CliAppSwitch_end = CliAppSwitch_sentinel;
#define cliapp__switch_is_end(S) \
  (0==memcmp(S, &CliAppSwitch_end, sizeof(CliAppSwitch)))
int cliapp_switch_is_end(CliAppSwitch const *s){
  return cliapp__switch_is_end(s);
}

CliAppSwitch const * cliapp_switch_for_arg(CliAppArg const * arg,
                                           int alsoFlag){
  CliAppSwitch const * a = cliApp.switches;
  for(; !cliapp__switch_is_end(a); ++a){
    if((alsoFlag==0 || arg->dash==a->dash)
       && 0==strcmp(a->key, arg->key)){
       return a;
    }
  }
  return 0;
}

CliAppArg const * cliapp_arg_next_same(CliAppArg const * arg){
  CliAppArg const * a = arg;
  CliAppArg const * tail = cliApp.argv + cliApp.argc;
  if(arg < cliApp.argv || arg >= tail){
    return 0;
  }
  for( ++a; a < tail; ++a ){
    if(a->key && 0==strcmp(a->key, arg->key)) return a;
  }
  return 0;
}

CliAppArg const * cliapp_arg_nonflag(){
  CliAppArg const * p = 0;
  for(; cliApp.cursorNonflag < cliApp.argc;){
    p = cliApp.argv + cliApp.cursorNonflag;
    ++cliApp.cursorNonflag;
    if(p->key && 0==p->dash){
      return p;
    }
  }
  return 0;
}

void cliapp_arg_nonflag_rewind(){
  cliApp.cursorNonflag = 1;
}

CliAppArg const * cliapp_arg_flag(char const * key1,
                                  char const * key2,
                                  int *atPos){
  int i = atPos ? *atPos : 1;
  assert(key1 || key2);
  if(!key1 & !key2) return 0;
  for( ; i < cliApp.argc; ++i ){
    CliAppArg const * p = cliApp.argv + i;
    if(!p->key) continue;
    else if((key1 && 0==strcmp(key1,p->key))
             ||
             (key2 && 0==strcmp(key2,p->key))){
      if(atPos) *atPos = i;
      return p;
    }
  }
  return 0;
}

char const * cliapp_flag_prefix( int flag ){
  char const * flags[4] = {"+",  "", "-", "--"};
  assert(flag>=-1 && flag<=2);
  return flag>=-1 && flag<=2 ? flags[flag+1] : "";
}

void cliapp_switches_visit( CliAppSwitch_visitor_f visitor,
                            void * state ){
  CliAppSwitch const * s = cliApp.switches;
  assert(s);
  for( ; !cliapp__switch_is_end(s); ++s){
    if(visitor(s, state)) break;
  }  
}

void cliapp_args_visit( CliAppArg_visitor_f visitor, void * state,
                        unsigned short skipArgs ){
  int i = skipArgs;
  CliAppArg const * a = i <  cliApp.argc
    ? &cliApp.argv[i] : NULL;
  for( ; i < cliApp.argc; ++i, ++a ){
    if(a->key && visitor(a, i, state)) break;
  }
}

void cliapp_printv(char const *fmt, va_list vargs){
  if(cliApp.print){
    cliApp.print(fmt, vargs);
  }
}

void cliapp_print(char const *fmt, ...){
  if(cliApp.print){
    va_list vargs;
    va_start(vargs,fmt);
    cliApp.print(fmt, vargs);
    va_end(vargs);
  }    
}

void cliapp_warn(char const *fmt, ...){
    va_list vargs;
    va_start(vargs,fmt);
    vfprintf(stderr, fmt, vargs);
    va_end(vargs);
}

#if 0
static void cliapp_perr(char const *fmt, ...){
    va_list vargs;
    va_start(vargs,fmt);
    cliapp_verrmsg(fmt,vargs);
    va_end(vargs);
    va_start(vargs,fmt);
    vfprintf(stderr, fmt, vargs);
    va_end(vargs);
}
#endif

/**
   The list of arguments pointed to by cliApp.argv.
*/
static CliAppArg cliAppArgv[CLIAPP_ARGV_COUNT];
/**
   An internal buffer used to store --flag keys for those keys which
   require transformation. This is: for --flag=value, we copy the
   "flag" part to this buffer so we can NUL-terminate it. For flags
   with no '=' we simply refer to the original argv string pointers,
   as those are NUL terminated. (We "could" modify the original
   globals instead, but the thought of doing so makes me a ill.)
*/
static char cliAppKeyBuf[CLIAPP_ARGV_KBUF_SIZE]
/* Store all NUL-terminated flag keys here, stripped of their
   leading dashes and terminated at their '=' (if they had
   one). */;

/**
   Internal helper for cliapp_process_argv() which checks list to see
   if s is contained in it. list must be terminated by a NULL pointer.
*/
static char cliapp_check_seen( CliAppSwitch const * const * list,
                               CliAppSwitch const * s ){
  int i = 0;
  CliAppSwitch const * p;
  assert(s);
  while(1){
    p = list[i++];
    if(p == s) return 1;
    else if(!p) break;
  }
  return 0;
}

int cliapp_process_argv(int argc, char const * const * argv,
                        unsigned int reserved ){
  /*static CliAppSwitch const * sPending = 0;
    static CliAppArg * aPending = 0;*/
  int i, rc = 0, doubleDashPos = 0;
  char * k = &cliAppKeyBuf[0];
  char const *errMsg = 0;
  CliAppSwitch const * switchPendingVal = 0
    /* Switch with the CLIAPP_F_SPACE_VALUE which is expecting
       a value in the next argument. */;
  CliAppArg * argPendingVal = 0
    /* arg counterpart of switchPendingVal */;
  CliAppSwitch const * appSwitch = 0;
  CliAppSwitch const * seenList[CLIAPP_ARGV_COUNT+1]
    /* switches we've seen so far, so we can check for/enforce
       the CLIAPP_F_ONCE flag */;
  int seenCount = 0 /* number of entries in seenList */;
  if(reserved){/*unused*/}
  memset(seenList, 0, sizeof(seenList));
  assert(cliApp.switches
         && "cliApp.switches must be set by the client "
         "before calling this.");
  cliApp.argv = &cliAppArgv[0];
  memset(&cliAppArgv[0], 0, sizeof(cliAppArgv));
#define KCHECK if(k>=&cliAppKeyBuf[0]+CLIAPP_ARGV_KBUF_SIZE) goto err_overflow
#define BADFLAG(MSG) errMsg=MSG; goto misuse
  for(i = 0; i < argc && 0==doubleDashPos; ++i){
    CliAppArg * p;
    char const * arg;
    int dashes = 0 /* number of dashes on the current flag */;
    int callbackIndex = i
      /* index to pass to callbacks. Gets modified in one case */;
    if(i == CLIAPP_ARGV_COUNT){
      cliapp_errmsg("Too many (%d) arguments: internal buffer "
                    "limit (%d) would be reached.", argc,
                    CLIAPP_ARGV_COUNT);
      return CLIAPP_RC_RANGE;
    }
    appSwitch = 0;
    p = cliApp.argv + i;
    arg = argv[i];
    if(*arg=='+'){
      dashes = -1;
      ++arg;
    }
    while(*arg && '-'==*arg && dashes<4){
      if(-1==dashes){
        dashes = 3 /* trigger error below */;
        break;
      }
      ++dashes;
      ++arg;
    }
    if(dashes>2){
      BADFLAG("Too many dashes on flag.");
    }
    p->dash = dashes;
    p->key = dashes ? k : arg
      /* dashed args get stored, without dashes, in cliAppKeyBuf so
         that we can terminate them with a NUL at their '='.  Args
         with no dashes are referenced to as-is - there's no need to
         copy them for termination purposes.  We don't know, at this
         point, whether a '=' is pending, but we pessimistically
         (optimistically?) assume there is.
      */;
    while(*arg && '='!=*arg){
      if(dashes){
        *k++ = *arg++;
        KCHECK;
      }      
      else ++arg;
    }
    if(*arg && !*p->key){
      BADFLAG("Empty flag name.");
    }
    KCHECK;
    if(*arg){
      assert('='==*arg);
      if(-1==dashes && '='==*arg){
        BADFLAG("+flags may not have a value.");
      }
      p->value = ++arg;
    }else if(!*p->key && 2==dashes && !doubleDashPos){
      doubleDashPos = i;
      p->key = p->value = 0;
    }
    if(dashes){
      *k++ = 0;
    }
    if(doubleDashPos && !switchPendingVal){
      /* Once we've encountered --, we must not continue to process
         flags because a flag after -- is typically intended for some
         downstream process, not the app on whose behalf we're
         running, and might semantically collide with flags from our
         own app. Also, such flags might have syntaxes we cannot
         handle, so even processing them as flags is potentially a
         bug. Rather than process them, we point cliApp.doubleDash.*
         to that state so the client can deal with it (possibly be
         passing it back into this function!).

         If switchPendingVal is not NULL we need to fall through to
         catch the error case of a missing value at the end of the
         arguments.
      */
      break;
    }else if(!doubleDashPos){
      ++cliApp.argc;
    }
    if(0){
      MARKER(("Arg #%d %d %s %s %s\n",
              i, p->dash, p->key,
              p->value ? "=" : "",
              p->value ? p->value : ""));
    }
    if(switchPendingVal){
      assert(argPendingVal);
      if(p->dash){
        appSwitch = switchPendingVal /* for error string */;
        BADFLAG("Got a flag while the previous flag was expecting "
                "a value");
      }else{
        argPendingVal->value = p->key;
        p->key = p->value = 0;
        p->dash = 0;
        p = argPendingVal /* for the upcoming callback(s) */;
        argPendingVal = 0;
        switchPendingVal = 0;
        --callbackIndex;
      }
    }
    assert(!doubleDashPos);
    if(p->dash && p->key){ /* -flag/--flag/+flag */
#if defined(DEBUG)
      static int check = 0;
      check = callbackIndex;
      assert(cliapp_arg_flag(p->key, 0, &check));
      assert(check == callbackIndex);
#endif
      appSwitch = cliapp_switch_for_arg(p, 1);
      if(!appSwitch){
        BADFLAG("Unknown flag");
      }
      else if((appSwitch->pflags & CLIAPP_F_ONCE)
              && cliapp_check_seen(seenList, appSwitch)){
        BADFLAG("Flag may only be provided once.");
      }
      else if(appSwitch->value && !p->value){
        if(i==argc-1){
          BADFLAG("Flag expecting a value at the end of the arguments");
        }
        switchPendingVal = appSwitch;
        argPendingVal = p;
      }else if(appSwitch->callback){
        rc = appSwitch->callback(callbackIndex, appSwitch, p);
        if(rc) return rc;
      }
      if(!switchPendingVal){
        seenList[seenCount++] = appSwitch;
      }
    }else{
      assert(cliapp_arg_nonflag()==p);
    }
    if(!switchPendingVal && cliApp.argCallack){
      rc = cliApp.argCallack(callbackIndex, appSwitch, p);
      if(rc) return rc;
    }
  }/*for(each arg)*/
  if(cliApp.argCallack){
    rc = cliApp.argCallack(i, 0, 0);
    if(rc) return rc;
  }

#undef BADFLAG
#undef KCHECK
  cliApp.cursorNonflag = 1 /* skip argv[0] */;
  if(doubleDashPos && doubleDashPos+1<argc){
    cliApp.doubleDash.argc = argc - doubleDashPos - 1;
    cliApp.doubleDash.argv = &argv[doubleDashPos+1];
  }
  return 0;
  misuse:
  cliapp_errmsg("Argument #%d: %s%s%s" /* (1) */
                "%s" /*(2)*/
                "%s%.*s" /*(3)*/
                "%s%.*s" /*(4)*/,
                /*(1)*/
                i+1, errMsg, (i<argc) ? ": " : "",
                (i<argc) ? argv[i] : "",
                /*(2)*/ 
                appSwitch ? ", flag: " : "",
                /*(3)*/ 
                appSwitch ? cliapp_flag_prefix(appSwitch->dash) : "",
                (CLIAPP_MSGBUF_SIZE/4),
                appSwitch ? appSwitch->key : "",
                /*(4)*/ 
                appSwitch && appSwitch->value ? "=" : "",
                CLIAPP_MSGBUF_SIZE/4,
                appSwitch ? appSwitch->value : "");
  return CLIAPP_RC_FLAG;
  err_overflow:
  cliapp_errmsg("Too many CLI flag keys: their accumulated names "
                "would overrun our %d-byte buffer.",
                CLIAPP_ARGV_KBUF_SIZE);
  return CLIAPP_RC_RANGE;
}

char * cliapp_lineedit_read(char const * prompt){
#if CLIAPP_ENABLE_LINENOISE
  return linenoise(prompt);
#elif CLIAPP_ENABLE_READLINE
  return readline(prompt);
#else
  if(prompt){/*avoid unused param warning*/}
  return 0;
#endif
}

void cliapp_lineedit_free(char * line){
#if CLIAPP_ENABLE_LINENOISE || CLIAPP_ENABLE_READLINE
  free(line);
#else
  if(line){
    assert(!"Where did this memory come from?");
  }
#endif
}

int cliapp_lineedit_load(char const * fname){
  if(!fname) fname = cliApp.lineread.historyFile;
  if(!fname || !*fname) return 0;
#if CLIAPP_ENABLE_LINENOISE
  return linenoiseHistoryLoad(fname);
#elif CLIAPP_ENABLE_READLINE
  return read_history(fname);
#else
  if(fname){/*avoid unused param warning*/}
  return CLIAPP_RC_UNSUPPORTED;
#endif
}

int cliapp_lineedit_save(char const * fname){
  int rc = 0;
  if(!fname) fname = cliApp.lineread.historyFile;
  if(!cliApp.lineread.needsSave
     || !fname || !*fname) return 0;
#if CLIAPP_ENABLE_LINENOISE
  rc = linenoiseHistorySave(fname) ? CLIAPP_RC_IO : 0;
#elif CLIAPP_ENABLE_READLINE
  rc = write_history(fname) ? CLIAPP_RC_IO : 0;
#else
  if(fname){/*avoid unused param warning*/}
  rc = CLIAPP_RC_UNSUPPORTED;
#endif
  if(!rc) cliApp.lineread.needsSave = 0;
  return rc;
}

int cliapp_lineedit_add(char const * line){
  assert(line);
#if CLIAPP_ENABLE_LINENOISE
  cliApp.lineread.needsSave = 1;
  linenoiseHistoryAdd(line);
  return 0;
#elif CLIAPP_ENABLE_READLINE
  cliApp.lineread.needsSave = 1;
  add_history(line);
  return 0;
#else
  if(line){/*avoid unused param warning*/}
  return CLIAPP_RC_UNSUPPORTED;
#endif
}

int cliapp_repl(CliApp_repl_f f, char const * const * prompt,
                int addHistoryPolicy, void * state){
  char * line;
  int rc = 0;
  while( !rc && (line = cliapp_lineedit_read(*prompt)) ){
    if(addHistoryPolicy<0) cliapp_lineedit_add(line);
    rc = f(line, state);
    if(!rc && addHistoryPolicy>0) cliapp_lineedit_add(line);
    cliapp_lineedit_free(line);
  }
  return rc;
}

#undef cliapp__switch_is_end
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<








































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted bindings/s2/cliapp.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#ifndef NET_WH_CLIAPP_H_INCLUDED
#define NET_WH_CLIAPP_H_INCLUDED
/**
   A mini-framework for handling some of the grunt work required by
   CLI apps. It's main intent is to provide a halfway sane system for
   handling CLI flags. It also provides an abstraction for CLI
   editing, supporting either libreadline or liblinenoise, but only
   supporting the most basic of editing facilities, not
   library-specific customizations (e.g. custom key bindings).

   This API has no required dependencies beyond the C89 standard
   libraries and requires no dynamic memory unless it's configured to
   use an interactive line-reading backend.

   License: Public Domain

   Author: Stephan Beal <stephan@wanderinghorse.net>
*/

#include <stdarg.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
   Flags for use with the CliAppSwitch::pflags field to modify
   how cliapp_process_argv() handles the switch.
*/
enum cliapp_switch_flags {
/**
   Indicates that a CliAppSwitch is only allowed to be provided once.
   If encountered more than once by cliapp_process_argv(), an error
   is triggered.
*/
CLIAPP_F_ONCE = 1,
/**
   Indicates that a given CliAppSwitch requires a value and accepts
   its value either in the form (-switch=value) or (-switch value).
   When such a flag is encountered, cliapp_process_argv() will report
   an error if the switch has no value (even if the flag appears at
   the end of the arguments list or immediately before the
   special-case "--" flag).

   Without this flag, switch values are only recognized in the form
   (-switch=flag).
*/
CLIAPP_F_SPACE_VALUE = 2
/*TODO?: CLIAPP_F_VALUE_REQUIRED = 4 (implied by CLIAPP_F_SPACE_VALUE) */
};

/**
   Result codes used by various library routines.
*/
enum cliapp_rc {
/** The non-error code */
CLIAPP_RC_OK = 0,
/** Indicates an error in flag processing. */
CLIAPP_RC_FLAG = -1,
/** Indicates a range-related error, e.g. buffer overrun or too many
    CLI arguments. */
CLIAPP_RC_RANGE = -2,
/** Indicates that an unsupported operation was requested. */
CLIAPP_RC_UNSUPPORTED = -3,
/** Indicates some sort of I/O error. */
CLIAPP_RC_IO = -4
};


/**
   Holds state for a single CLI argument, be it a flag or non-flag.
*/
struct CliAppArg {
  /**
     Must be 1 for single-dash flags, 2 for double-dash flags, and -1
     for '+' flags, and 0 for non-flags.
  */
  int dash;
  /**
     Fow switches, this holds the switch's key, without dashes. For
     non-switches, it holds the argument's value.
  **/
  char const * key;
  /**
     For switches, the description of their value (if any) is stored here.
     For non-switches, this is 0.
  */
  char const * value;
  /**
     Arbitrary value which may be set/used by the client. It's not
     used/modifed by this API.
  */
  int opaque;
};
typedef struct CliAppArg CliAppArg;

struct CliAppSwitch;

/**
   A callback type used by cliapp_process_argv() to notify the app
   when a CLI argument is processed which matches one of the app's
   defined flags. It is passed the argument index, the switch (if any)
   and the argument.

   Note that when processing switches flagged with
   CLIAPP_F_SPACE_VALUE, the indexes passed to this function might
   have gaps, as they skip over the VAL part of (-f VAL), instead
   effectively transforming that to (-f=VAL) before calling the
   callback for the -f switch.

   If the switch argument is NULL, the argument will be a non-flag
   value. Note that arguments starting at the special-case "--" flag
   are not passed on to the callback. Instead, such arguments get
   reported via the cliApp.doubleDash member.

   It gets passed two NULL values one time at the end of processing in
   order to allow the client code to do any final validation.

   If it returns non-0, cliapp_process_argv() will fail and return the
   callback's result.
*/
typedef int (*CliAppSwitch_callback_f)(int ndx, struct CliAppSwitch const * appSwitch,
                                       CliAppArg * arg);
/**
   Models a single flag/switch for a CLI app. It's not called
   CliAppFlag because that proved confusing together with CliAppArg.
*/
struct CliAppSwitch {
  /**
     Can be used by clients to, e.g. group help items by type or
     set various levels of help verbosity.
  */
  int opaque;
  /**
     As documented for CliAppArg::dash.
  */
  int dash;
  /**
     The flag name, without leading dashes.
  */
  char const * key;
  /**
     A human-readable description of its expected value, or 0 if the
     flag does not require a value.

     BUG? It may still be assigned a value by the caller. We
     currently require that behaviour for a special-case arg handler
     in s2sh2.
  */
  char const * value;
  /**
     Brief help text.
  */
  char const * brief;
  /**
     Optional detailed help text.
  */
  char const * details;

  /**
     Optional callback to be passed a CliAppArg instance after it's been
     initialized and confirmed as being a valid arg (defined in
     cliApp.switches). If cliApp.argCallack is also used, both
     callbacks are called, but this one is called first.
  */
  CliAppSwitch_callback_f callback;

  /**
     Reserved for future use by the cliapp interface, e.g. marking
     "has seen this flag before" in order to implement only-once
     behaviour.
  */
  int pflags;
};
typedef struct CliAppSwitch CliAppSwitch;

/**
   Client-defined CliApp.argv arrays MUST end with an entry identical
   to this one. The iteration-related APIs treat any entry which
   memcmp()'s as equivalent to this entry as being the end of th list.

   @see cliapp_switch_is_end()
*/
#define CliAppSwitch_sentinel {0,0,0,0,0,0,0,0}

/**
   Returns true (non-0) if the given object memcmp()'s as equivalent
   to CliAppSwitch_sentinel.
 */
int cliapp_switch_is_end(CliAppSwitch const *s);

/**
   vprintf()-compatible logging/printing interface for use with
   CliApp.
*/
typedef int (*CliApp_print_f)(char const *, va_list);

/**
   A callback for use with cliapp_switches_visit(). It is passed the
   switch object and an arbitrary state pointer provided by the
   caller of that function.   
*/
typedef int (*CliAppSwitch_visitor_f)(CliAppSwitch const *, void *);

/**
   Global app state. This class is intended to represent a singleton,
   the cliApp object.
*/
struct CliApp {
  /**
     Number of arguments in this->argv. It is modified as
     cliapp_process_argv() executes and only counts arguments
     up to, but not including the special-case "--" flag.
  */
  int argc;

  /**
     Arguments processed by cliapp_process_argv(). Contains
     this->argc entries. This memory is not valid until
     cliapp_process_argv() has succeeded.
  */
  CliAppArg * argv;

  /**
     Internal cursor for traversing non-flag arguments using
     cliapp_arg_nonflag(). Holds the *next* index to be used by that
     function.
  */
  int cursorNonflag;

  /**
     May be set to an error description by certain APIs and it may
     point to memory which can mutate.
  */
  char const * errMsg;

  /**
    Must be set up by the client *before* calling
    cliapp_process_argv() and its final entry MUST be an object for
    which cliapp_switch_is_end() returns true (that's how we know when
    to stop processing).
  */
  CliAppSwitch const * switches;

  /**
     If this is non-NULL, cliapp_print() and friends will use it for
     output, otherwise they will elide all output. This defaults to
     vprintf().
  */
  CliApp_print_f print;

  /**
     If set, it gets called each time cliapp_process_argv() processes
     an argument. If it returns non-0, processing fails.

     After processing successfully completes, the callback is called
     one final time with NULL arguments so that the callback can
     perform any end-of-list validation or whatnot.

     Using this callback effectively turns cliapp_process_argv() into
     a push parser, which turns out to be a pretty convenient way to
     handle CLI flags.
  */
  CliAppSwitch_callback_f argCallack;

  /**
     If cliapp_process_argv() encounters the "--" flag, and additional
     arguments follow it, this object gets filled out with information
     about them.

     Note that encountering "--" with no following arugments is not
     considered an error.
  */
  struct {
    /**
       If cliapp_process_argv() encounters "--", this value gets set
       to the number of arguments available in the original argv array
       immediately following (but not including) the "--" flag
    */
    int argc;
    /**
       If cliapp_process_argv() encounters "--", and there are
       arguments after it, this value is set to the list of arguments
       (from the original argv array) immediately following the "--"
       flag. If "--" is not encountered, or there are no arguments
       after it, this member's value is 0.
    */
    char const * const * argv;
  } doubleDash;
  
  /**
     State related to interactive line-editing/reading.
  */
  struct {
    /**
       If enabled at compile-time, this has a value of 1 (for
       linenoise) or 2 (for readline), else it has a value of 0.

       To use libreadline, compile this code's C file with
       CLIAPP_ENABLE_READLINE set to a true value. To use linenoise,
       build with CLIAPP_ENABLE_LINENOISE set to a true value.
    */
    int const enabled;
    /**
       If non-NULL, cliapp_lineedit_save(NULL) will use this name for
       saving.
    */
    char const * historyFile;
    /**
       Specifies whether or not the line editing history has been
       modified since the last save.

       This initially has a value of 0 and it gets set to non-0 if
       cliapp_lineedit_add() is called.
    */
    int needsSave;
  } lineread;
};

/**
   Behold! The One True Instance of CliApp!
*/
extern struct CliApp cliApp;

/**
   Visits all switches in cliApp.switches, calling
   visitor(theSwitch,state) for each one. If the visitor returns
   non-0, visitation halts without an error.

   It stops iterating when it encounters an entry for which
   cliapp_switch_is_end() returns true.
*/
void cliapp_switches_visit( CliAppSwitch_visitor_f visitor,
                            void * state );

/**
   Callback signature for use with cliapp_args_visit().

   It gets passed the CLI argument, the index of that argument in
   cliApp.argv, and an optional client-specified state pointer.
*/
typedef int (*CliAppArg_visitor_f)(CliAppArg const *, int ndx, void *);

/**
   Visits all args in cliApp.argv, calling visitor(theSwitch,itsIndex,state)
   for each one. If skipArgs is greater than 0, that many are skipped
   over before visiting. Behaviour is undefined if a visitor modifies
   cliApp.argv or cliApp.argc. If the visitor returns non-0,
   visitation halts without an error.

   CliAppArg entries with a NULL key are skipped over, under the assumption
   that the client app has marked them as "removed".
*/
void cliapp_args_visit( CliAppArg_visitor_f visitor, void * state,
                        unsigned short skipArgs );

/**
   Initializes the argument-processing parts of the cliApp global
   object with. It is intended to be passed the conventional argc/argv
   arguments which are passed to the application's main().

   The final parameter is reserved for future use in providing flags
   to change this function's behaviour. A value of 0 is reserved as
   meaning "the default behaviour."

   cliApp.switches must have been assigned to non-NULL before calling
   this, or behaviour is undefined. If any given switch has a callback
   assigned to it, it will be called when that switch is processed,
   and processing fails if it returns non-0. (Potential TODO: allow a
   NULL switches value to simply treat all flags a known switches.)

   If cliApp.argCallack is not-NULL, it is called for every
   argument. It will be passed the CLI argument and, if it's a flag,
   its corresponding CliAppSwitch instance (extracted from
   cliApp.switches). For non-flag arguments, a NULL CliAppSwitch is
   passed to it. If it returns non-0, processing fails. If processes
   completes successfully, the callback is called one additional time
   with NULL pointer values to indicate that the end has been
   reached. This can be used to handle post-argument cleanup, perform
   app-specific argument validation, or similar.

   If callbacks are set both on the switch and cliApp, both are called
   in that order, but only the cliApp callback is called one final
   time after processing is done.

   If this function returns 0, the client may manipulate the contents
   of cliApp.argv, within reason, but must be certain to keep
   cliApp.argc in sync with that list's entries.

   On error a non-0 code is returned, either propagated from a
   callback or (if the error originates from this function) an entry
   from the cliapp_rc enum. In the latter case, cliapp_err_get() will
   contain information about why it failed.

   Encountering an argument which is neither a non-flag nor a flag
   defined in cliApp.switches results in an error.

   Quirks:

   - Arguments after "--" are NOT processed by this
   function. Processing them would be a bug-in-waiting because those
   flags might collide with app-level flags and/or require syntaxes
   which this code treats as an error, e.g. using three dashes instead
   of 1 or 2. Instead, if "--" is encounter, cliApp.doubleDash is
   populated with information about the flags so the client may deal
   with them (which might mean passing them back into this routine!).

   - All argv-related cliApp state is reset on each call, so if this
   function is called multiple times, any client-side pointers
   referring to cliApp's state may then point to different information
   than they expect and/or may become stale pointers. (cliApp-held
   data, e.g. cliApp.argv, keeps the same pointers but re-populates
   the state, but the lifetime of external pointers,
   e.g. cliApp.doubleDash.argv, is client-dependent.)
*/
int cliapp_process_argv(int argc, char const * const * argv,
                        unsigned int reserved);

/**
   If cliApp.print is not NULL, this passes on its arguments to that
   function, else this is a no-op.
*/
void cliapp_printv(char const *fmt, va_list);

/**
   Elipses-args form of cliapp_printv().
*/
void cliapp_print(char const *fmt, ...);

/**
   Outputs a printf-formatted message to stderr.
*/
void cliapp_warn(char const *fmt, ...);

/**
   Returns the next entry in cliApp.argv which is a non-flag argument,
   skipping over argv[0]. Returns 0 when the end of the list is
   reached.
*/
CliAppArg const * cliapp_arg_nonflag();

/**
   Resets the traversal of cliapp_arg_nonflag() to start from
   the beginning.
*/
void cliapp_arg_nonflag_rewind();

/**
   If the given argument matches an app-configured flag, that flag is
   returned, else 0 is returned.

   If alsoFlag is true, the first argument and the corresponding
   switch must also have matching flag values to be considered a
   match.
*/
CliAppSwitch const * cliapp_switch_for_arg(CliAppArg const * arg,
                                           int alsoFlag);

/**
   Searches for a flag matching one of the given keys. Each entry
   in cliApp.argv is checked, in order, against both of the given
   keys, in the order they are provided.

   The conventional way to call it is to pass the short-form flag,
   then the long-form flag, but that's just a convention.

   Either of the first two arguments may be NULL but both may not be
   NULL.

   If the 3rd parameter is not NULL then:

   1) *atPos indicates an index position to start the search at. (Note
   that it should initially be 1, not 0, in order to skip over the
   app's name, stored in argv[0].)

   2) If non-NULL is returned, *atPos is set to the index at which the
   argument was found. If NULL is returned, *argPos is not modified.

   Thus atPos can be used to iterate through multiple copies of a
   flag, noting that its value points to the index at which the
   previous entry was found, so needs to be incremented by 1 before
   each subsequent iteration

   On a match, the corresponding CliAppArg is returned, else 0 is
   returned.
*/
CliAppArg const * cliapp_arg_flag(char const * key1, char const * key2,
                                  int * atPos);

/**
   Given a flag value for a CliAppArg or CliAppSwitch, this
   returns a prefix string depending on that value:

   1 = "-", 2 = "--", 3 = "+"

   Anything else = "". The returned bytes are static.
*/
char const * cliapp_flag_prefix( int flag );

/**
   Given a CliAppArg, presumably one from cliapp_arg_flag() or
   cliapp_arg_nonflag(), this searches for the next argument with the
   same key.

   If the given argument is from outside cliApp.argv's memory range,
   or is the last element in that list, 0 is returned.

   Bug? For non-flag arguments this does not update the internal
   non-flag traversal cursor.
*/
CliAppArg const * cliapp_arg_next_same(CliAppArg const * arg);

/**
   Clears any error state in the cliApp object.
*/
void cliapp_err_clear();

/**
   If cliApp has a current error message set, it is returned, else 0
   is returned. The memory is static and its contents may be modified
   by any calls into this API.
*/
char const * cliapp_err_get();


/**
   Tries to save the line-editing history to the given filename, or to
   cliApp.lineedit.historyFile if fname is NULL. If both are NULL or
   empty, or if cliApp.lineedit.needsSave is 0, this is a no-op and
   returns 0. Returns CLIAPP_RC_UNSUPPORTED if line-editing is not
   enabled.
*/
int cliapp_lineedit_save(char const * fname);

/**
   Adds the given line to the line-edit history. If this function
   returns 0, it also sets cliApp.lineedit.needsSave to a non-0 value.

   Returns 0 on success or CLIAPP_RC_UNSUPPORTED if line-editing is
   not enabled.
*/
int cliapp_lineedit_add(char const * line);

/**
   Tries to load the line-editing history from the given filename, or
   to cliApp.lineedit.historyFile if fname is NULL. If both are NULL
   or empty, this is a no-op. Returns CLIAPP_RC_UNSUPPORTED if
   line-editing is not enabled. If the underlying line-editing backend
   returns an error, CLIAPP_RC_IO is returned, under the assumption
   that there was a problem with reading the file (e.g. unreadable),
   as opposed to an allocation error or similar.
*/
int cliapp_lineedit_load(char const * fname);

/**
   If cliApp.lineedit.enabled is true, this function passes its
   argument to free(3), else it will (in debug builds) trigger an
   assert if passed non-NULL. This must be called once for each line
   fetched via cliapp_lineedit_read().
*/
void cliapp_lineedit_free(char * line);

/**
   If line-editing is enabled, this reads a single line using that
   back-end and returns the new string, which must be passed to
   cliapp_lineedit_free() after the caller is done with it.

   Returns 0 if line-editing is not enabled or if the caller taps the
   platform's EOF sequence (Ctrl-D on Unix) at the start of the
   line. Returns an empty string if the user simply taps ENTER.

   TODO: if no line-editing backend is built in, fall back to fgets()
   on stdin. It ain't pretty, but it'll do in a pinch.
*/
char * cliapp_lineedit_read(char const * prompt);

/**
   Callback type for use with cliapp_repl().
*/
typedef int (*CliApp_repl_f)(char const * line, void * state);

/**
   Enters a REPL (Read, Eval, Print Loop). Each iteration does
   the following:

   1) Fetch an input line using cliapp_lineedit_read(), passing it
   *prompt. If that returns NULL, this function returns 0.

   2) If addHistoryPolicy is <0 then the read line is added to the
   history.

   3) Calls callback(theReadLine, state).

   4) If (3) returns 0 and addHistoryPolicy is >0, the read line
   is added to the history.

   5) Passes the read line to cliapp_lineedit_free().

   6) If (3) returns non-0, this function returns that value.

   Notes:

   - The prompt is a pointer to a pointer so that the caller may
   modify it between loop iterations. This function derefences *prompt
   on each iteration.

   - An addHistoryPolicy of 0 means that this function will not
   automatically add input lines to the history. The callback is free
   to do so.

   - This function never passes a NULL line value to the callback but
   it may pass an empty line.
*/
int cliapp_repl(CliApp_repl_f callback, char const * const * prompt,
                int addHistoryPolicy, void * state);

#ifdef __cplusplus
}
#endif
#endif /* NET_WH_CLIAPP_H_INCLUDED */

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted bindings/s2/config.h.

1
2
3
4
5
#define S2_OS_UNIX 1
#define S2_HAVE_REALPATH 1
#define S2_HAVE_STAT 1
#define S2_HAVE_CHDIR 1
#define CWAL_OBASE_ISA_HASH 1
<
<
<
<
<










Deleted bindings/s2/f-s2sh.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/*
  Initialization script for s2sh. If it is named the same as the binary
  (minus any ".exe" extension), with a ".s2" extension (and in the same directory),
  s2sh will autoload this file at startup and fail if processing it fails.
*/

assert Fossil;
assert Fossil.Context;

assert s2 && 'function' === typename s2.loadModule;
/**
  An s2.loadModule() proxy which uses a PathFinder
  instance to search for DLLs. The name argument must be the
  base name part, optionally with a partial leading (sub-)path.

  The dest argument is passed as the last argument to s2.loadModule(),
  and is returned to the caller on success. Well-behaved modules will
  install their features in that object.

  If the S2_MODULE_PATH and/or S2_MODULE_EXTENSION environment
  variables are set, they are treated as a semicolon- or
  colon-separated list of directories resp. file extensions. If not
  specified, some default set is used.

*/
s2.loadModule2 = function(name, dest = {}){
    affirm 'string' === typename name;
    const fn = pf.search( name );
    fn || throw "Cannot find '".
        concat(name, "' in search path ", pf.prefix.toJSONString());
    //print("Importing",name, '==>', fn);
    return loadModule.call(this, realpath ? realpath(fn) : fn, dest);
}.importSymbols({
    loadModule: s2.loadModule,
    realpath: s2.io ? s2.io.realpath : undefined,
    pf: s2.PathFinder.new(
        // Directories...
        ('string' === typename (var s = s2.getenv('S2_MODULE_PATH')))
            ? s.split(s.indexOf(';') >= 0 ? ';' : ':')
        : ['.'],
        // Extensions...
        ('string' === typename (s = s2.getenv('S2_MODULE_EXTENSIONS')))
       ? s.split(s.indexOf(';') >= 0 ? ';' : ':')
        : ['.so','.dll']
    )
});


/**
   For the given container, v, this displays (via s2.io.output() a
   listing of its properties. Intended for getting an overview of an
   object's API.
*/
s2.vls = proc(v,label){
    label && print(label);
    typeinfo(iscontainer v) && this.eachProperty.call(v,eachProp);
}.importSymbols({
    eachProp:proc(k,v){
        print('\t'+typeinfo(name k), k, '=', typeinfo(name v), v);
    }
});

/**
   For the given container, v, this displays (via s2.io.output() a
   tree listing, recursively, of its properties. Intended for getting
   an overview of an object's API.

   If includeProto is truthy, v.prototype is also dumped. If it is
   less than 0, that is done recursively for all entries and their
   prototypes.
*/
s2.vtree = proc vtree(v,label,includeProto){
    typeinfo(iscontainer v) || return;
    if(!typeinfo(isstring label) && undefined===includeProto){
        includeProto= label;
        label = undefined;
    }
    const doMyProto = includeProto;
    includeProto < 0 || (includeProto=0);
    //print(__FLC,'argv =',argv);
    label ?: (label = "%1$p".applyFormat(v));
    label && out(buf.toString(),label," [type: ",typeinfo(name v),'] ==>\n');
    ++buf.level;
    buf.length(buf.level*4);
    buf.fill(' ');
    var ex = catch{
        // ^^^^ s2 bug: braces around this catch should not be necessary.
        // Symptom: if the first if() passes then the 'else' is getting seen
        // after catch resolves.
        if(typeinfo(isarray v)){
            //out(' array ',v.toJSONString(0,true/*output cycles as strings*/),'\n');
            v.eachIndex(eachIndex);
        }else{
            buf.prototype.eachProperty.call(v,eachProp);
        }
    };
    ex = catch if(!ex && v.prototype){
        doMyProto
        ? vtree(v.prototype,label?label+'.prototype':0, includeProto)
        : out(buf.toString(),label ? label+'.' : '',
              'prototype ==> [type: ',
              typeinfo(name v.prototype),']\n');
    };
    --buf.level;
    buf.length(buf.level * 4);
    ex && throw ex;
}.importSymbols({
    buf: eval {
        var b = new s2.Buffer(20);
        b.level = 0;
        b;
    },
    out: s2.io.output,
    eachProp:proc(k,v){
        if(typeinfo(isfunction v)){
            out(buf.toString(), (label ? label+'.'+k :k),'()\n');
        }
        else if(typeinfo(iscontainer v)
                && typeinfo(isfunction v.mayIterate)){
            v.mayIterate()
                ? vtree(v,(label ? label+'.'+k : k), includeProto)
                : out(buf.toString(),(label ? label+'.'+k : k),
                      " = <cyclic: %1$p>\n".applyFormat(v));
        }else{
            out(buf.toString(), (label ? label+'.'+k :k),
                ' = ', typeinfo(name v), ' ', v, '\n');
        }
    },
    eachIndex:proc(v,k){
        if(typeinfo(isfunction v)){
            out(buf.toString(), (label ? label+'['+k+']','()\n'));
        }
        else if(typeinfo(iscontainer v)
                && typeinfo(isfunction v.mayIterate)){
            v.mayIterate()
                ? vtree(v,(label ? label+'['+k+']' : k), includeProto)
                : out(buf.toString(),(label ? label+'['+k+']' : k),
                      " = <cyclic: %1$p>\n".applyFormat(v));
        }else{
            out(buf.toString(), (label ? label+'['+k+']' :k),
                ' = ', typeinfo(name v), ' ', v, '\n');
        }
    }
});

/**
   Add Fossil.require(), used in loading Fossil-aware modules.
*/
Fossil.require = import( false, 'require.d/require.s2' );

// The rest of the initialization happens via here:
Fossil.require(['nocache!fsl/extendFossil'],proc(){});
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
















































































































































































































































































































Deleted bindings/s2/fslcgi.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/bin/sh
########################################################################
# Intended to be run as a CGI script. It is currently hard-coded to
# live in same dir as s2sh and the ./fslcgi.d directory and f-s2sh
# binary.
#
# A sample Apache vhost for this script looks like:
#
#<VirtualHost *:80>
#    ServerName s2.local
#    ScriptAlias /cgi-bin/ /path/to/libfossil/s2
#    DocumentRoot /path/to/libfossil/s2
#    Options +FollowSymLinks
#</VirtualHost>
#<Directory "/path/to/libfossil/s2">
#        AllowOverride All
#        Options +ExecCGI +Indexes
#        Order allow,deny
#        Allow from all
#        Require all granted
#        DirectoryIndex fslcgi
#        SetHandler cgi-script
#</Directory>
########################################################################

S2_HOME=`dirname "$0"`
# PROXY_SCRIPT is the script-side entry point for the CGI app
PROXY_SCRIPT=$0.s2
# Interpreter-level flags for s2sh:
S2_SHELL_FLAGS="-S -a -R"
S2_SHELL=$S2_HOME/f-s2sh
#S2_RC_DIR = "resource dir" for cgi bits. Passed via the --resource-dir CLI flag.
S2_RC_DIR=${S2_HOME}/fslcgi.d

S2_MODULE_PATH=".:$S2_HOME:${S2_RC_DIR}/lib"
S2_INCLUDES_PATH=".:$S2_HOME:${S2_RC_DIR}/include"
#NYI: S2_AUTOLOAD_MODULES="module1,module2,module3"

export S2_MODULE_PATH S2_INCLUDES_PATH S2_AUTOLOAD_MODULES

#export S2_HOME

# For libfossil.so:
LD_LIBRARY_PATH="${S2_HOME}/..:${LD_LIBRARY_PATH}"
export LD_LIBRARY_PATH

# Tell the script world which repository file to use...
if [ -d ~/fossil ]; then
    S2_CGI_REPO="~/fossil/libfossil.fsl"
    true
else
    S2_CGI_REPO="$S2_HOME/../../libfossil.fsl"
    #S2_CGI_REPO="-R=$S2_HOME/../../fossil.fsl"
fi

#S2_CGI_REP0=''
# Misc flags intended for consumption by scripts...
S2_REPO_URL=http://fossil.wanderinghorse.net/repos/libfossil/
#S2_CGI_REPO=-C
S2_SCRIPT_FLAGS="--repo-db=${S2_CGI_REPO} --repo-url=${S2_REPO_URL} --resource-dir=${S2_RC_DIR}"
#S2_SCRIPT_FLAGS="${S2_SCRIPT_FLAGS} --repo-url=http://url-to-your/repo[.cgi]"

exec $S2_SHELL ${S2_SHELL_FLAGS} -f $PROXY_SCRIPT -- $S2_SCRIPT_FLAGS
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






























































































































Deleted bindings/s2/fslcgi.d/init.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
/**
   Post-bootstrap initialization code for s2's fslcgi. Must live in the
   root of "resource directory", but that (with sufficient hacking) may
   live outside of the web-root.
*/
affirm 'undefined' !== typename $CGI /* expecting CGI module to be loaded under this name */;
affirm Fossil.require;


$CGI.config = {
    resourceDir: Fossil.file.canonicalName(__FILEDIR,true),
    cgiRoot: '<TODO:cgiRoot>'
};
scope {
    const C = $CGI;
    var uri = s2.getenv('SCRIPT_NAME');
    // Set up cgiRoot (the "standard" way of getting the root dir
    // for link-building purposes). This kludgery is not generic...
    if(uri) { C.config.cgiRoot = uri + '/' }
    else{
        uri = s2.getenv('REQUEST_URI');
        C.request.ENV || (C.request.REQUEST_URI = uri);
        if(uri){
            var head = uri.split('fslcgi',2).0;//, head = adj.0;
            C.config.cgiRoot = head ||| '/empty';
            //('/' === head) ? head : head + '/'; //.concat('fslcgi', '/');
        }
    }
    
    C.config.localServerMode =
        (var serverName = s2.getenv('SERVER_NAME'))
        && (0<serverName.indexOf(".local"));
    ;;
}

/**
   A convenience alias for s2.io.output (or functionally
   equivalent).
*/
$CGI.out = s2.io.output;

/**
   Sends its RHS argument output to the (buffered) response body.
   Returns this object (so it can be chained).
*/
$CGI.'operator<<' = proc(self,arg){
    return s2.io.output(arg), this;
};

/**
    Removes "potentially security-relevant" properties from the
    exception ex. Returns ex.
*/
$CGI.scrubException = proc(ex){
    if(this.config.scrubExceptions){
        unset ex.script, ex.stackTrace, ex.line, ex.column // security-relevant
    }
    return ex;
};

$CGI.util = {
    /**
       Converts all key/value pairs from obj to a string of urlencoded
       key value pairs, separated by '&'. If addQMark is true then the
       result is prefixed by a '?', otherwise it is not.

       If obj has no properties then an empty string is returned,
       regardless of addQMark.

       Property key order in the result string is unspecified.
    */
    objToUrlOpt: proc(obj, addQMark = false){
        affirm obj && obj.eachProperty /* expecting an Object */;
        buf.reset();
        var counter = 0;
        obj.eachProperty(each);
        return buf.isEmpty()
            ? ''
            : (addQMark ? '?' : '')+buf.toString();
    }.importSymbols({
        buf: s2.Buffer.new(),
        each: proc(k,v){
            counter++ && buf.append('&');
            buf.append($CGI.urlencode(''+k),'=',$CGI.urlencode(''+v));
            /* Reminder to self: $CGI.urlencode() and friends require
               that $CGI be their 'this' because they reuse an internal
               buffer on each call to save on allocations.
            */
        }
    }),

    absoluteLink: proc(path,label=path, urlOpt){
        affirm 'string' === typename path;
        const hasQ = path.indexOf('?')>=0,
          optStr = this.objToUrlOpt(urlOpt, !hasQ );
        return fmt.applyFormat(
            $CGI.config.cgiRoot,
            $CGI.urlencode(path)+(optStr ? hasQ ? '&' + optStr : optStr : ''),
            label);
    }.importSymbols({
        fmt: "<a href='%1$s%2$s'>%3$s</a>"
    });
};


$CGI.resourcePath = proc(name){
    return this.config.resourceDir + name;
};

$CGI.resolveResource = proc(name){
    const path = this.config.resourceDir + name;
    return Fossil.file.isFile(path) ? path : undefined;
};

// incomplete... not yet sure what i want.
$CGI.request.pathInfoList = scope {
    var ps, p = s2.getenv('PATH_INFO');
    if(p && '/'!==p){
        ps = p.split('/');
        // Trim leading/trailing null entries caused
        // by leading/trailing slashes:
        ps.0 || ps.shift();
        ps[ps.length()-1] || ps.pop();
    }
    ps;
};

if(0){ // only for testing
    $CGI.setContentType('application/json');
    print({request: $CGI.request,
          config: $CGI.config}.toJSONString(2));
}else if(1){
    const c = $CGI;
    c.setContentType('text/plain');
    print("$CGI sanity checks...");
    c << 'Fossil.time.cpuTime() says: '
        << Fossil.time.cpuTime()
        << '\n'
    ;

    const f = c.fossilInstance;
    f.db || f.openCheckout();
    c << 'Fossil context: ' << f << ' repo db: ' << f.db.repo.filename << '\n';
    c << "pathInfoList = "<<c.request.pathInfoList<<'\n';
    c << "resourcePath(foo/bar.baz)==>"
        << c.resourcePath('foo/bar.baz')
        << '\n';
    c << "resolveResource('init.s2')==>"
        << c.resolveResource('init.s2')
        << '\n';
    c << "resolveResource('x')==>"
        << c.resolveResource('x')
        << '\n';

    if(1) {
        c << '$CGI.config = ' << $CGI.config.toJSONString(2) << '\n';
    }

    c << "Most recent timeline event: "
        << f.db.selectRow('SELECT * FROM event ORDER BY mtime DESC')
        .toJSONString(2)
        << '\n';

    if(0){
        // Workaround: c is a Native, and those are rejected
        // by the C-level toJSON bits:
        var obj = {};
        c.eachProperty(proc(k,v){obj[k]=v});
        c << '$CGI properties:\n' << obj.toJSONString(4) << '\n';
    }
    if(1){
        c << 'objToUrlOpt: '
            << c.util.objToUrlOpt({a:3,c:'a b'})
            << '\n';
        c << 'objToUrlOpt again (should be empty): '
            << c.util.objToUrlOpt({},true)
            << '\n';
        c << 'absoluteLink: '
            << c.util.absoluteLink('foo/bar/baz?x=y', 'link', {a:1,b:'hi !'})
            << '\n';
        c << 'absoluteLink again: '
            << c.util.absoluteLink('foo/bar/', 'link', {a:1,b:'hi !'})
            << '\n';
        c << 'Fossil.artifactTypes = '<<Fossil.artifactTypes<<'\n';
        
    }

    0 && Fossil.require(['tmpl-compiled!layout/default.tmpl.s2'],proc(t){
        t.evalContents('default layout');
    });

    c << '\nThe end. Fossil.time.cpuTime() says: '
        << Fossil.time.cpuTime()
        << '\n';

    //throw exception(-666,"testing discarding of buffered output");
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










































































































































































































































































































































































































Deleted bindings/s2/fslcgi.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
   Bootstrap script for the s2/libfossil CGI bits.
*/
const $CGI = scope {
    /* Load the CGI module and bail out with an HTTP-correct response if it fails. */
    var c;
    var err = catch{
        c = s2.loadModule2('cgimod').cgi;
        affirm c;
        affirm 'function' === typename c.send;
    };
    if(err){
        const out = s2.io.output;
        out("Status: 500 Could not load CGI module.\r\n");
        out("Content-type: text/plain\r\n");
        out("\r\n");
        if(1){
            // potentially security-relevant (absolute paths, etc)
            out(err.toJSONString(2));
        }else{
            out("Could not load CGI module.");
        }
        exit;
    }
    c;
};
//print('cgi =',typename $CGI);
/* Start output buffering before the page gets a chance to output anything. */
const $ob = s2.ob;
$ob.push();
var err = catch Fossil.require(
['cliargs','fsl/context', 'fsl/db/repoOrCheckout'/*to get it opened*/],
proc(cliargs, F){
    // TODO: get our routing/dispatching in place, and import/call it here...
    const resDir = cliargs.takeFlag('resource-dir');
    typeinfo(isstring resDir) || throw "Set the --resource-dir=/path SCRIPT flag (after --) in the main CGI script.";
    $CGI.fossilInstance = F;
    requireS2.getPlugin('default').prefix[] = resDir;
    requireS2.getPlugin('tmpl').suffix[] = '.tmpl.s2';
    requireS2.getPlugin('tmpl-compiled').suffix[] = '.tmpl.s2';
    s2.import(Fossil.file.canonicalName(resDir,false)+'/init.s2');
    //$CGI.dispatchRoute();
    //$CGI.setContentType('text/plain');
    //$CGI.request.PATH_INFO = s2.getenv('PATH_INFO') ||| 'try adding a path';
    //print('(ammended) $CGI.request:');
    //print($CGI.request.toJSONString(2));
    //throw "This causes generated (buffered) output to be discarded.";
});

if(err){ // transform exception to a JSON object.
    // discard accumulated output, but keep one buffering level for our own use...
    var obLevel = $ob.level();
    for( ; obLevel > 1; $ob.pop(), --obLevel) {}
    if(obLevel){ $ob.clear() }
    else { $ob.push() /* so our headers get sent properly */ }
    if($CGI.httpStatus() < 500){
        $CGI.httpStatus(500, "Caught Exception");
    }
    $CGI.scrubException && (err = $CGI.scrubException(err));
    $CGI.setContentType('application/json');
    print({exception:err}.toJSONString(-1));
}
$CGI.send() /* will flush all $ob buffers after emiting headers */;
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






























































































































Deleted bindings/s2/linenoise/linenoise.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
/* linenoise.c -- guerrilla line editing library against the idea that a
 * line editing lib needs to be 20,000 lines of C code.
 *
 * You can find the latest source code at:
 *
 *   http://github.com/msteveb/linenoise
 *   (forked from http://github.com/antirez/linenoise)
 *
 * Does a number of crazy assumptions that happen to be true in 99.9999% of
 * the 2010 UNIX computers around.
 *
 * ------------------------------------------------------------------------
 *
 * Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
 * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
 * Copyright (c) 2011, Steve Bennett <steveb at workware dot net dot au>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *  *  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *  *  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ------------------------------------------------------------------------
 *
 * References:
 * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
 * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
 *
 * Bloat:
 * - Completion?
 *
 * Unix/termios
 * ------------
 * List of escape sequences used by this program, we do everything just
 * a few sequences. In order to be so cheap we may have some
 * flickering effect with some slow terminal, but the lesser sequences
 * the more compatible.
 *
 * EL (Erase Line)
 *    Sequence: ESC [ n K
 *    Effect: if n is 0 or missing, clear from cursor to end of line
 *    Effect: if n is 1, clear from beginning of line to cursor
 *    Effect: if n is 2, clear entire line
 *
 * CUF (CUrsor Forward)
 *    Sequence: ESC [ n C
 *    Effect: moves cursor forward of n chars
 *
 * CR (Carriage Return)
 *    Sequence: \r
 *    Effect: moves cursor to column 1
 *
 * The following are used to clear the screen: ESC [ H ESC [ 2 J
 * This is actually composed of two sequences:
 *
 * cursorhome
 *    Sequence: ESC [ H
 *    Effect: moves the cursor to upper left corner
 *
 * ED2 (Clear entire screen)
 *    Sequence: ESC [ 2 J
 *    Effect: clear the whole screen
 *
 * == For highlighting control characters, we also use the following two ==
 * SO (enter StandOut)
 *    Sequence: ESC [ 7 m
 *    Effect: Uses some standout mode such as reverse video
 *
 * SE (Standout End)
 *    Sequence: ESC [ 0 m
 *    Effect: Exit standout mode
 *
 * == Only used if TIOCGWINSZ fails ==
 * DSR/CPR (Report cursor position)
 *    Sequence: ESC [ 6 n
 *    Effect: reports current cursor position as ESC [ NNN ; MMM R
 *
 * win32/console
 * -------------
 * If __MINGW32__ is defined, the win32 console API is used.
 * This could probably be made to work for the msvc compiler too.
 * This support based in part on work by Jon Griffiths.
 */

#ifdef _WIN32 /* Windows platform, either MinGW or Visual Studio (MSVC) */
#include <windows.h>
#include <fcntl.h>
#define USE_WINCONSOLE
#ifdef __MINGW32__
#define HAVE_UNISTD_H
#else
/* Microsoft headers don't like old POSIX names */
#define strdup _strdup
#define snprintf _snprintf
#endif
#else
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#define USE_TERMIOS
#define HAVE_UNISTD_H
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>

#include "linenoise.h"
#include "utf8.h"

#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
#define LINENOISE_MAX_LINE 4096

#define ctrl(C) ((C) - '@')

/* Use -ve numbers here to co-exist with normal unicode chars */
enum {
    SPECIAL_NONE,
    SPECIAL_UP = -20,
    SPECIAL_DOWN = -21,
    SPECIAL_LEFT = -22,
    SPECIAL_RIGHT = -23,
    SPECIAL_DELETE = -24,
    SPECIAL_HOME = -25,
    SPECIAL_END = -26,
    SPECIAL_INSERT = -27,
    SPECIAL_PAGE_UP = -28,
    SPECIAL_PAGE_DOWN = -29
};

static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
static int history_len = 0;
static char **history = NULL;

/* Structure to contain the status of the current (being edited) line */
struct current {
    char *buf;  /* Current buffer. Always null terminated */
    int bufmax; /* Size of the buffer, including space for the null termination */
    int len;    /* Number of bytes in 'buf' */
    int chars;  /* Number of chars in 'buf' (utf-8 chars) */
    int pos;    /* Cursor position, measured in chars */
    int cols;   /* Size of the window, in chars */
    const char *prompt;
    char *capture; /* Allocated capture buffer, or NULL for none. Always null terminated */
#if defined(USE_TERMIOS)
    int fd;     /* Terminal fd */
#elif defined(USE_WINCONSOLE)
    HANDLE outh; /* Console output handle */
    HANDLE inh; /* Console input handle */
    int rows;   /* Screen rows */
    int x;      /* Current column during output */
    int y;      /* Current row */
#endif
};

static int fd_read(struct current *current);
static int getWindowSize(struct current *current);

void linenoiseHistoryFree(void) {
    if (history) {
        int j;

        for (j = 0; j < history_len; j++)
            free(history[j]);
        free(history);
        history = NULL;
        history_len = 0;
    }
}

#if defined(USE_TERMIOS)
static void linenoiseAtExit(void);
static struct termios orig_termios; /* in order to restore at exit */
static int rawmode = 0; /* for atexit() function to check if restore is needed*/
static int atexit_registered = 0; /* register atexit just 1 time */

static const char *unsupported_term[] = {"dumb","cons25",NULL};

static int isUnsupportedTerm(void) {
    char *term = getenv("TERM");

    if (term) {
        int j;
        for (j = 0; unsupported_term[j]; j++) {
            if (strcmp(term, unsupported_term[j]) == 0) {
                return 1;
            }
        }
    }
    return 0;
}

static int enableRawMode(struct current *current) {
    struct termios raw;

    current->fd = STDIN_FILENO;
    current->cols = 0;

    if (!isatty(current->fd) || isUnsupportedTerm() ||
        tcgetattr(current->fd, &orig_termios) == -1) {
fatal:
        errno = ENOTTY;
        return -1;
    }

    if (!atexit_registered) {
        atexit(linenoiseAtExit);
        atexit_registered = 1;
    }

    raw = orig_termios;  /* modify the original mode */
    /* input modes: no break, no CR to NL, no parity check, no strip char,
     * no start/stop output control. */
    raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    /* output modes - disable post processing */
    raw.c_oflag &= ~(OPOST);
    /* control modes - set 8 bit chars */
    raw.c_cflag |= (CS8);
    /* local modes - choing off, canonical off, no extended functions,
     * no signal chars (^Z,^C) */
    raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
    /* control chars - set return condition: min number of bytes and timer.
     * We want read to return every single byte, without timeout. */
    raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */

    /* put terminal in raw mode after flushing */
    if (tcsetattr(current->fd,TCSADRAIN,&raw) < 0) {
        goto fatal;
    }
    rawmode = 1;
    return 0;
}

static void disableRawMode(struct current *current) {
    /* Don't even check the return value as it's too late. */
    if (rawmode && tcsetattr(current->fd,TCSADRAIN,&orig_termios) != -1)
        rawmode = 0;
}

/* At exit we'll try to fix the terminal to the initial conditions. */
static void linenoiseAtExit(void) {
    if (rawmode) {
        tcsetattr(STDIN_FILENO, TCSADRAIN, &orig_termios);
    }
    linenoiseHistoryFree();
}

/* gcc/glibc insists that we care about the return code of write!
 * Clarification: This means that a void-cast like "(void) (EXPR)"
 * does not work.
 */
#define IGNORE_RC(EXPR) if (EXPR) {}

/* This is fdprintf() on some systems, but use a different
 * name to avoid conflicts
 */
static void fd_printf(int fd, const char *format, ...)
{
    va_list args;
    char buf[64];
    int n;

    va_start(args, format);
    n = vsnprintf(buf, sizeof(buf), format, args);
    va_end(args);
    IGNORE_RC(write(fd, buf, n));
}

static void clearScreen(struct current *current)
{
    fd_printf(current->fd, "\x1b[H\x1b[2J");
}

static void cursorToLeft(struct current *current)
{
    fd_printf(current->fd, "\r");
}

static int outputChars(struct current *current, const char *buf, int len)
{
    return write(current->fd, buf, len);
}

static void outputControlChar(struct current *current, char ch)
{
    fd_printf(current->fd, "\x1b[7m^%c\x1b[0m", ch);
}

static void eraseEol(struct current *current)
{
    fd_printf(current->fd, "\x1b[0K");
}

static void setCursorPos(struct current *current, int x)
{
    fd_printf(current->fd, "\r\x1b[%dC", x);
}

/**
 * Reads a char from 'fd', waiting at most 'timeout' milliseconds.
 *
 * A timeout of -1 means to wait forever.
 *
 * Returns -1 if no char is received within the time or an error occurs.
 */
static int fd_read_char(int fd, int timeout)
{
    struct pollfd p;
    unsigned char c;

    p.fd = fd;
    p.events = POLLIN;

    if (poll(&p, 1, timeout) == 0) {
        /* timeout */
        return -1;
    }
    if (read(fd, &c, 1) != 1) {
        return -1;
    }
    return c;
}

/**
 * Reads a complete utf-8 character
 * and returns the unicode value, or -1 on error.
 */
static int fd_read(struct current *current)
{
#ifdef USE_UTF8
    char buf[4];
    int n;
    int i;
    int c;

    if (read(current->fd, &buf[0], 1) != 1) {
        return -1;
    }
    n = utf8_charlen(buf[0]);
    if (n < 1 || n > 3) {
        return -1;
    }
    for (i = 1; i < n; i++) {
        if (read(current->fd, &buf[i], 1) != 1) {
            return -1;
        }
    }
    buf[n] = 0;
    /* decode and return the character */
    utf8_tounicode(buf, &c);
    return c;
#else
    return fd_read_char(current->fd, -1);
#endif
}

static int countColorControlChars(const char* prompt)
{
    /* ANSI color control sequences have the form:
     * "\x1b" "[" [0-9;]* "m"
     * We parse them with a simple state machine.
     */

    enum {
        search_esc,
        expect_bracket,
        expect_trail
    } state = search_esc;
    int len = 0, found = 0;
    char ch;

    /* XXX: Strictly we should be checking utf8 chars rather than
     *      bytes in case of the extremely unlikely scenario where
     *      an ANSI sequence is part of a utf8 sequence.
     */
    while ((ch = *prompt++) != 0) {
        switch (state) {
        case search_esc:
            if (ch == '\x1b') {
                state = expect_bracket;
            }
            break;
        case expect_bracket:
            if (ch == '[') {
                state = expect_trail;
                /* 3 for "\e[ ... m" */
                len = 3;
                break;
            }
            state = search_esc;
            break;
        case expect_trail:
            if ((ch == ';') || ((ch >= '0' && ch <= '9'))) {
                /* 0-9, or semicolon */
                len++;
                break;
            }
            if (ch == 'm') {
                found += len;
            }
            state = search_esc;
            break;
        }
    }

    return found;
}

/**
 * Stores the current cursor column in '*cols'.
 * Returns 1 if OK, or 0 if failed to determine cursor pos.
 */
static int queryCursor(int fd, int* cols)
{
    /* control sequence - report cursor location */
    fd_printf(fd, "\x1b[6n");

    /* Parse the response: ESC [ rows ; cols R */
    if (fd_read_char(fd, 100) == 0x1b &&
        fd_read_char(fd, 100) == '[') {

        int n = 0;
        while (1) {
            int ch = fd_read_char(fd, 100);
            if (ch == ';') {
                /* Ignore rows */
                n = 0;
            }
            else if (ch == 'R') {
                /* Got cols */
                if (n != 0 && n < 1000) {
                    *cols = n;
                }
                break;
            }
            else if (ch >= 0 && ch <= '9') {
                n = n * 10 + ch - '0';
            }
            else {
                break;
            }
        }
        return 1;
    }

    return 0;
}

/**
 * Updates current->cols with the current window size (width)
 */
static int getWindowSize(struct current *current)
{
    struct winsize ws;

    if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col != 0) {
        current->cols = ws.ws_col;
        return 0;
    }

    /* Failed to query the window size. Perhaps we are on a serial terminal.
     * Try to query the width by sending the cursor as far to the right
     * and reading back the cursor position.
     * Note that this is only done once per call to linenoise rather than
     * every time the line is refreshed for efficiency reasons.
     *
     * In more detail, we:
     * (a) request current cursor position,
     * (b) move cursor far right,
     * (c) request cursor position again,
     * (d) at last move back to the old position.
     * This gives us the width without messing with the externally
     * visible cursor position.
     */

    if (current->cols == 0) {
        int here;

        current->cols = 80;

        /* (a) */
        if (queryCursor (current->fd, &here)) {
            /* (b) */
            fd_printf(current->fd, "\x1b[999C");

            /* (c). Note: If (a) succeeded, then (c) should as well.
             * For paranoia we still check and have a fallback action
             * for (d) in case of failure..
             */
            if (!queryCursor (current->fd, &current->cols)) {
                /* (d') Unable to get accurate position data, reset
                 * the cursor to the far left. While this may not
                 * restore the exact original position it should not
                 * be too bad.
                 */
                fd_printf(current->fd, "\r");
            } else {
                /* (d) Reset the cursor back to the original location. */
                if (current->cols > here) {
                    fd_printf(current->fd, "\x1b[%dD", current->cols - here);
                }
            }
        } /* 1st query failed, doing nothing => default 80 */
    }

    return 0;
}

/**
 * If escape (27) was received, reads subsequent
 * chars to determine if this is a known special key.
 *
 * Returns SPECIAL_NONE if unrecognised, or -1 if EOF.
 *
 * If no additional char is received within a short time,
 * 27 is returned.
 */
static int check_special(int fd)
{
    int c = fd_read_char(fd, 50);
    int c2;

    if (c < 0) {
        return 27;
    }

    c2 = fd_read_char(fd, 50);
    if (c2 < 0) {
        return c2;
    }
    if (c == '[' || c == 'O') {
        /* Potential arrow key */
        switch (c2) {
            case 'A':
                return SPECIAL_UP;
            case 'B':
                return SPECIAL_DOWN;
            case 'C':
                return SPECIAL_RIGHT;
            case 'D':
                return SPECIAL_LEFT;
            case 'F':
                return SPECIAL_END;
            case 'H':
                return SPECIAL_HOME;
        }
    }
    if (c == '[' && c2 >= '1' && c2 <= '8') {
        /* extended escape */
        c = fd_read_char(fd, 50);
        if (c == '~') {
            switch (c2) {
                case '2':
                    return SPECIAL_INSERT;
                case '3':
                    return SPECIAL_DELETE;
                case '5':
                    return SPECIAL_PAGE_UP;
                case '6':
                    return SPECIAL_PAGE_DOWN;
                case '7':
                    return SPECIAL_HOME;
                case '8':
                    return SPECIAL_END;
            }
        }
        while (c != -1 && c != '~') {
            /* .e.g \e[12~ or '\e[11;2~   discard the complete sequence */
            c = fd_read_char(fd, 50);
        }
    }

    return SPECIAL_NONE;
}
#elif defined(USE_WINCONSOLE)

static DWORD orig_consolemode = 0;

static int enableRawMode(struct current *current) {
    DWORD n;
    INPUT_RECORD irec;

    current->outh = GetStdHandle(STD_OUTPUT_HANDLE);
    current->inh = GetStdHandle(STD_INPUT_HANDLE);

    if (!PeekConsoleInput(current->inh, &irec, 1, &n)) {
        return -1;
    }
    if (getWindowSize(current) != 0) {
        return -1;
    }
    if (GetConsoleMode(current->inh, &orig_consolemode)) {
        SetConsoleMode(current->inh, ENABLE_PROCESSED_INPUT);
    }
    return 0;
}

static void disableRawMode(struct current *current)
{
    SetConsoleMode(current->inh, orig_consolemode);
}

static void clearScreen(struct current *current)
{
    COORD topleft = { 0, 0 };
    DWORD n;

    FillConsoleOutputCharacter(current->outh, ' ',
        current->cols * current->rows, topleft, &n);
    FillConsoleOutputAttribute(current->outh,
        FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN,
        current->cols * current->rows, topleft, &n);
    SetConsoleCursorPosition(current->outh, topleft);
}

static void cursorToLeft(struct current *current)
{
    COORD pos = { 0, (SHORT)current->y };
    DWORD n;

    FillConsoleOutputAttribute(current->outh,
        FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN, current->cols, pos, &n);
    current->x = 0;
}

static int outputChars(struct current *current, const char *buf, int len)
{
    COORD pos = { (SHORT)current->x, (SHORT)current->y };
    DWORD n;

    WriteConsoleOutputCharacter(current->outh, buf, len, pos, &n);
    current->x += len;
    return 0;
}

static void outputControlChar(struct current *current, char ch)
{
    COORD pos = { (SHORT)current->x, (SHORT)current->y };
    DWORD n;

    FillConsoleOutputAttribute(current->outh, BACKGROUND_INTENSITY, 2, pos, &n);
    outputChars(current, "^", 1);
    outputChars(current, &ch, 1);
}

static void eraseEol(struct current *current)
{
    COORD pos = { (SHORT)current->x, (SHORT)current->y };
    DWORD n;

    FillConsoleOutputCharacter(current->outh, ' ', current->cols - current->x, pos, &n);
}

static void setCursorPos(struct current *current, int x)
{
    COORD pos = { (SHORT)x, (SHORT)current->y };

    SetConsoleCursorPosition(current->outh, pos);
    current->x = x;
}

static int fd_read(struct current *current)
{
    while (1) {
        INPUT_RECORD irec;
        DWORD n;
        if (WaitForSingleObject(current->inh, INFINITE) != WAIT_OBJECT_0) {
            break;
        }
        if (!ReadConsoleInput (current->inh, &irec, 1, &n)) {
            break;
        }
        if (irec.EventType == KEY_EVENT && irec.Event.KeyEvent.bKeyDown) {
            KEY_EVENT_RECORD *k = &irec.Event.KeyEvent;
            if (k->dwControlKeyState & ENHANCED_KEY) {
                switch (k->wVirtualKeyCode) {
                 case VK_LEFT:
                    return SPECIAL_LEFT;
                 case VK_RIGHT:
                    return SPECIAL_RIGHT;
                 case VK_UP:
                    return SPECIAL_UP;
                 case VK_DOWN:
                    return SPECIAL_DOWN;
                 case VK_INSERT:
                    return SPECIAL_INSERT;
                 case VK_DELETE:
                    return SPECIAL_DELETE;
                 case VK_HOME:
                    return SPECIAL_HOME;
                 case VK_END:
                    return SPECIAL_END;
                 case VK_PRIOR:
                    return SPECIAL_PAGE_UP;
                 case VK_NEXT:
                    return SPECIAL_PAGE_DOWN;
                }
            }
            /* Note that control characters are already translated in AsciiChar */
            else if (k->wVirtualKeyCode == VK_CONTROL)
	        continue;
            else {
#ifdef USE_UTF8
                return k->uChar.UnicodeChar;
#else
                return k->uChar.AsciiChar;
#endif
            }
        }
    }
    return -1;
}

static int countColorControlChars(const char* prompt)
{
    /* For windows we assume that there are no embedded ansi color
     * control sequences.
     */
    return 0;
}

static int getWindowSize(struct current *current)
{
    CONSOLE_SCREEN_BUFFER_INFO info;
    if (!GetConsoleScreenBufferInfo(current->outh, &info)) {
        return -1;
    }
    current->cols = info.dwSize.X;
    current->rows = info.dwSize.Y;
    if (current->cols <= 0 || current->rows <= 0) {
        current->cols = 80;
        return -1;
    }
    current->y = info.dwCursorPosition.Y;
    current->x = info.dwCursorPosition.X;
    return 0;
}
#endif

static int utf8_getchars(char *buf, int c)
{
#ifdef USE_UTF8
    return utf8_fromunicode(buf, c);
#else
    *buf = c;
    return 1;
#endif
}

/**
 * Returns the unicode character at the given offset,
 * or -1 if none.
 */
static int get_char(struct current *current, int pos)
{
    if (pos >= 0 && pos < current->chars) {
        int c;
        int i = utf8_index(current->buf, pos);
        (void)utf8_tounicode(current->buf + i, &c);
        return c;
    }
    return -1;
}

static void refreshLine(const char *prompt, struct current *current)
{
    int plen;
    int pchars;
    int backup = 0;
    int i;
    const char *buf = current->buf;
    int chars = current->chars;
    int pos = current->pos;
    int b;
    int ch;
    int n;

    /* Should intercept SIGWINCH. For now, just get the size every time */
    getWindowSize(current);

    plen = strlen(prompt);
    pchars = utf8_strlen(prompt, plen);

    /* Scan the prompt for embedded ansi color control sequences and
     * discount them as characters/columns.
     */
    pchars -= countColorControlChars(prompt);

    /* Account for a line which is too long to fit in the window.
     * Note that control chars require an extra column
     */

    /* How many cols are required to the left of 'pos'?
     * The prompt, plus one extra for each control char
     */
    n = pchars + utf8_strlen(buf, current->len);
    b = 0;
    for (i = 0; i < pos; i++) {
        b += utf8_tounicode(buf + b, &ch);
        if (ch < ' ') {
            n++;
        }
    }

    /* If too many are needed, strip chars off the front of 'buf'
     * until it fits. Note that if the current char is a control character,
     * we need one extra col.
     */
    if (current->pos < current->chars && get_char(current, current->pos) < ' ') {
        n++;
    }

    while (n >= current->cols && pos > 0) {
        b = utf8_tounicode(buf, &ch);
        if (ch < ' ') {
            n--;
        }
        n--;
        buf += b;
        pos--;
        chars--;
    }

    /* Cursor to left edge, then the prompt */
    cursorToLeft(current);
    outputChars(current, prompt, plen);

    /* Now the current buffer content */

    /* Need special handling for control characters.
     * If we hit 'cols', stop.
     */
    b = 0; /* unwritted bytes */
    n = 0; /* How many control chars were written */
    for (i = 0; i < chars; i++) {
        int ch;
        int w = utf8_tounicode(buf + b, &ch);
        if (ch < ' ') {
            n++;
        }
        if (pchars + i + n >= current->cols) {
            break;
        }
        if (ch < ' ') {
            /* A control character, so write the buffer so far */
            outputChars(current, buf, b);
            buf += b + w;
            b = 0;
            outputControlChar(current, ch + '@');
            if (i < pos) {
                backup++;
            }
        }
        else {
            b += w;
        }
    }
    outputChars(current, buf, b);

    /* Erase to right, move cursor to original position */
    eraseEol(current);
    setCursorPos(current, pos + pchars + backup);
}

static void set_current(struct current *current, const char *str)
{
    strncpy(current->buf, str, current->bufmax);
    current->buf[current->bufmax - 1] = 0;
    current->len = strlen(current->buf);
    current->pos = current->chars = utf8_strlen(current->buf, current->len);
}

static int has_room(struct current *current, int bytes)
{
    return current->len + bytes < current->bufmax - 1;
}

/**
 * Removes the char at 'pos'.
 *
 * Returns 1 if the line needs to be refreshed, 2 if not
 * and 0 if nothing was removed
 */
static int remove_char(struct current *current, int pos)
{
    if (pos >= 0 && pos < current->chars) {
        int p1, p2;
        int ret = 1;
        p1 = utf8_index(current->buf, pos);
        p2 = p1 + utf8_index(current->buf + p1, 1);

#ifdef USE_TERMIOS
        /* optimise remove char in the case of removing the last char */
        if (current->pos == pos + 1 && current->pos == current->chars) {
            if (current->buf[pos] >= ' ' && utf8_strlen(current->prompt, -1) + utf8_strlen(current->buf, current->len) < current->cols - 1) {
                ret = 2;
                fd_printf(current->fd, "\b \b");
            }
        }
#endif

        /* Move the null char too */
        memmove(current->buf + p1, current->buf + p2, current->len - p2 + 1);
        current->len -= (p2 - p1);
        current->chars--;

        if (current->pos > pos) {
            current->pos--;
        }
        return ret;
    }
    return 0;
}

/**
 * Insert 'ch' at position 'pos'
 *
 * Returns 1 if the line needs to be refreshed, 2 if not
 * and 0 if nothing was inserted (no room)
 */
static int insert_char(struct current *current, int pos, int ch)
{
    char buf[3];
    int n = utf8_getchars(buf, ch);

    if (has_room(current, n) && pos >= 0 && pos <= current->chars) {
        int p1, p2;
        int ret = 1;
        p1 = utf8_index(current->buf, pos);
        p2 = p1 + n;

#ifdef USE_TERMIOS
        /* optimise the case where adding a single char to the end and no scrolling is needed */
        if (current->pos == pos && current->chars == pos) {
            if (ch >= ' ' && utf8_strlen(current->prompt, -1) + utf8_strlen(current->buf, current->len) < current->cols - 1) {
                IGNORE_RC(write(current->fd, buf, n));
                ret = 2;
            }
        }
#endif

        memmove(current->buf + p2, current->buf + p1, current->len - p1);
        memcpy(current->buf + p1, buf, n);
        current->len += n;

        current->chars++;
        if (current->pos >= pos) {
            current->pos++;
        }
        return ret;
    }
    return 0;
}

/**
 * Captures up to 'n' characters starting at 'pos' for the cut buffer.
 *
 * This replaces any existing characters in the cut buffer.
 */
static void capture_chars(struct current *current, int pos, int n)
{
    if (pos >= 0 && (pos + n - 1) < current->chars) {
        int p1 = utf8_index(current->buf, pos);
        int nbytes = utf8_index(current->buf + p1, n);

        if (nbytes) {
            free(current->capture);
            /* Include space for the null terminator */
            current->capture = (char *)malloc(nbytes + 1);
            memcpy(current->capture, current->buf + p1, nbytes);
            current->capture[nbytes] = '\0';
        }
    }
}

/**
 * Removes up to 'n' characters at cursor position 'pos'.
 *
 * Returns 0 if no chars were removed or non-zero otherwise.
 */
static int remove_chars(struct current *current, int pos, int n)
{
    int removed = 0;

    /* First save any chars which will be removed */
    capture_chars(current, pos, n);

    while (n-- && remove_char(current, pos)) {
        removed++;
    }
    return removed;
}
/**
 * Inserts the characters (string) 'chars' at the cursor position 'pos'.
 *
 * Returns 0 if no chars were inserted or non-zero otherwise.
 */
static int insert_chars(struct current *current, int pos, const char *chars)
{
    int inserted = 0;

    while (*chars) {
        int ch;
        int n = utf8_tounicode(chars, &ch);
        if (insert_char(current, pos, ch) == 0) {
            break;
        }
        inserted++;
        pos++;
        chars += n;
    }
    return inserted;
}

#ifndef NO_COMPLETION
static linenoiseCompletionCallback *completionCallback = NULL;

static void beep() {
#ifdef USE_TERMIOS
    fprintf(stderr, "\x7");
    fflush(stderr);
#endif
}

static void freeCompletions(linenoiseCompletions *lc) {
    size_t i;
    for (i = 0; i < lc->len; i++)
        free(lc->cvec[i]);
    free(lc->cvec);
}

static int completeLine(struct current *current) {
    linenoiseCompletions lc = { 0, NULL };
    int c = 0;

    completionCallback(current->buf,&lc);
    if (lc.len == 0) {
        beep();
    } else {
        size_t stop = 0, i = 0;

        while(!stop) {
            /* Show completion or original buffer */
            if (i < lc.len) {
                struct current tmp = *current;
                tmp.buf = lc.cvec[i];
                tmp.pos = tmp.len = strlen(tmp.buf);
                tmp.chars = utf8_strlen(tmp.buf, tmp.len);
                refreshLine(current->prompt, &tmp);
            } else {
                refreshLine(current->prompt, current);
            }

            c = fd_read(current);
            if (c == -1) {
                break;
            }

            switch(c) {
                case '\t': /* tab */
                    i = (i+1) % (lc.len+1);
                    if (i == lc.len) beep();
                    break;
                case 27: /* escape */
                    /* Re-show original buffer */
                    if (i < lc.len) {
                        refreshLine(current->prompt, current);
                    }
                    stop = 1;
                    break;
                default:
                    /* Update buffer and return */
                    if (i < lc.len) {
                        set_current(current,lc.cvec[i]);
                    }
                    stop = 1;
                    break;
            }
        }
    }

    freeCompletions(&lc);
    return c; /* Return last read character */
}

/* Register a callback function to be called for tab-completion.
   Returns the prior callback so that the caller may (if needed)
   restore it when done. */
linenoiseCompletionCallback * linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) {
    linenoiseCompletionCallback * old = completionCallback;
    completionCallback = fn;
    return old;
}

void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) {
    lc->cvec = (char **)realloc(lc->cvec,sizeof(char*)*(lc->len+1));
    lc->cvec[lc->len++] = strdup(str);
}

#endif

static int linenoiseEdit(struct current *current) {
    int history_index = 0;

    /* The latest history entry is always our current buffer, that
     * initially is just an empty string. */
    linenoiseHistoryAdd("");

    set_current(current, "");
    refreshLine(current->prompt, current);

    while(1) {
        int dir = -1;
        int c = fd_read(current);

#ifndef NO_COMPLETION
        /* Only autocomplete when the callback is set. It returns < 0 when
         * there was an error reading from fd. Otherwise it will return the
         * character that should be handled next. */
        if (c == '\t' && current->pos == current->chars && completionCallback != NULL) {
            c = completeLine(current);
            /* Return on errors */
            if (c < 0) return current->len;
            /* Read next character when 0 */
            if (c == 0) continue;
        }
#endif

process_char:
        if (c == -1) return current->len;
#ifdef USE_TERMIOS
        if (c == 27) {   /* escape sequence */
            c = check_special(current->fd);
        }
#endif
        switch(c) {
        case '\r':    /* enter */
            history_len--;
            free(history[history_len]);
            return current->len;
        case ctrl('C'):     /* ctrl-c */
            errno = EAGAIN;
            return -1;
        case 127:   /* backspace */
        case ctrl('H'):
            if (remove_char(current, current->pos - 1) == 1) {
                refreshLine(current->prompt, current);
            }
            break;
        case ctrl('D'):     /* ctrl-d */
            if (current->len == 0) {
                /* Empty line, so EOF */
                history_len--;
                free(history[history_len]);
                return -1;
            }
            /* Otherwise fall through to delete char to right of cursor */
        case SPECIAL_DELETE:
            if (remove_char(current, current->pos) == 1) {
                refreshLine(current->prompt, current);
            }
            break;
        case SPECIAL_INSERT:
            /* Ignore. Expansion Hook.
             * Future possibility: Toggle Insert/Overwrite Modes
             */
            break;
        case ctrl('W'):    /* ctrl-w, delete word at left. save deleted chars */
            /* eat any spaces on the left */
            {
                int pos = current->pos;
                while (pos > 0 && get_char(current, pos - 1) == ' ') {
                    pos--;
                }

                /* now eat any non-spaces on the left */
                while (pos > 0 && get_char(current, pos - 1) != ' ') {
                    pos--;
                }

                if (remove_chars(current, pos, current->pos - pos)) {
                    refreshLine(current->prompt, current);
                }
            }
            break;
        case ctrl('R'):    /* ctrl-r */
            {
                /* Display the reverse-i-search prompt and process chars */
                char rbuf[50];
                char rprompt[80];
                int rchars = 0;
                int rlen = 0;
                int searchpos = history_len - 1;

                rbuf[0] = 0;
                while (1) {
                    int n = 0;
                    const char *p = NULL;
                    int skipsame = 0;
                    int searchdir = -1;

                    snprintf(rprompt, sizeof(rprompt), "(reverse-i-search)'%s': ", rbuf);
                    refreshLine(rprompt, current);
                    c = fd_read(current);
                    if (c == ctrl('H') || c == 127) {
                        if (rchars) {
                            int p = utf8_index(rbuf, --rchars);
                            rbuf[p] = 0;
                            rlen = strlen(rbuf);
                        }
                        continue;
                    }
#ifdef USE_TERMIOS
                    if (c == 27) {
                        c = check_special(current->fd);
                    }
#endif
                    if (c == ctrl('P') || c == SPECIAL_UP) {
                        /* Search for the previous (earlier) match */
                        if (searchpos > 0) {
                            searchpos--;
                        }
                        skipsame = 1;
                    }
                    else if (c == ctrl('N') || c == SPECIAL_DOWN) {
                        /* Search for the next (later) match */
                        if (searchpos < history_len) {
                            searchpos++;
                        }
                        searchdir = 1;
                        skipsame = 1;
                    }
                    else if (c >= ' ') {
                        if (rlen >= (int)sizeof(rbuf) + 3) {
                            continue;
                        }

                        n = utf8_getchars(rbuf + rlen, c);
                        rlen += n;
                        rchars++;
                        rbuf[rlen] = 0;

                        /* Adding a new char resets the search location */
                        searchpos = history_len - 1;
                    }
                    else {
                        /* Exit from incremental search mode */
                        break;
                    }

                    /* Now search through the history for a match */
                    for (; searchpos >= 0 && searchpos < history_len; searchpos += searchdir) {
                        p = strstr(history[searchpos], rbuf);
                        if (p) {
                            /* Found a match */
                            if (skipsame && strcmp(history[searchpos], current->buf) == 0) {
                                /* But it is identical, so skip it */
                                continue;
                            }
                            /* Copy the matching line and set the cursor position */
                            set_current(current,history[searchpos]);
                            current->pos = utf8_strlen(history[searchpos], p - history[searchpos]);
                            break;
                        }
                    }
                    if (!p && n) {
                        /* No match, so don't add it */
                        rchars--;
                        rlen -= n;
                        rbuf[rlen] = 0;
                    }
                }
                if (c == ctrl('G') || c == ctrl('C')) {
                    /* ctrl-g terminates the search with no effect */
                    set_current(current, "");
                    c = 0;
                }
                else if (c == ctrl('J')) {
                    /* ctrl-j terminates the search leaving the buffer in place */
                    c = 0;
                }
                /* Go process the char normally */
                refreshLine(current->prompt, current);
                goto process_char;
            }
            break;
        case ctrl('T'):    /* ctrl-t */
            if (current->pos > 0 && current->pos <= current->chars) {
                /* If cursor is at end, transpose the previous two chars */
                int fixer = (current->pos == current->chars);
                c = get_char(current, current->pos - fixer);
                remove_char(current, current->pos - fixer);
                insert_char(current, current->pos - 1, c);
                refreshLine(current->prompt, current);
            }
            break;
        case ctrl('V'):    /* ctrl-v */
            if (has_room(current, 3)) {
                /* Insert the ^V first */
                if (insert_char(current, current->pos, c)) {
                    refreshLine(current->prompt, current);
                    /* Now wait for the next char. Can insert anything except \0 */
                    c = fd_read(current);

                    /* Remove the ^V first */
                    remove_char(current, current->pos - 1);
                    if (c != -1) {
                        /* Insert the actual char */
                        insert_char(current, current->pos, c);
                    }
                    refreshLine(current->prompt, current);
                }
            }
            break;
        case ctrl('B'):
        case SPECIAL_LEFT:
            if (current->pos > 0) {
                current->pos--;
                refreshLine(current->prompt, current);
            }
            break;
        case ctrl('F'):
        case SPECIAL_RIGHT:
            if (current->pos < current->chars) {
                current->pos++;
                refreshLine(current->prompt, current);
            }
            break;
        case SPECIAL_PAGE_UP:
          dir = history_len - history_index - 1; /* move to start of history */
          goto history_navigation;
        case SPECIAL_PAGE_DOWN:
          dir = -history_index; /* move to 0 == end of history, i.e. current */
          goto history_navigation;
        case ctrl('P'):
        case SPECIAL_UP:
            dir = 1;
          goto history_navigation;
        case ctrl('N'):
        case SPECIAL_DOWN:
history_navigation:
            if (history_len > 1) {
                /* Update the current history entry before to
                 * overwrite it with tne next one. */
                free(history[history_len - 1 - history_index]);
                history[history_len - 1 - history_index] = strdup(current->buf);
                /* Show the new entry */
                history_index += dir;
                if (history_index < 0) {
                    history_index = 0;
                    break;
                } else if (history_index >= history_len) {
                    history_index = history_len - 1;
                    break;
                }
                set_current(current, history[history_len - 1 - history_index]);
                refreshLine(current->prompt, current);
            }
            break;
        case ctrl('A'): /* Ctrl+a, go to the start of the line */
        case SPECIAL_HOME:
            current->pos = 0;
            refreshLine(current->prompt, current);
            break;
        case ctrl('E'): /* ctrl+e, go to the end of the line */
        case SPECIAL_END:
            current->pos = current->chars;
            refreshLine(current->prompt, current);
            break;
        case ctrl('U'): /* Ctrl+u, delete to beginning of line, save deleted chars. */
            if (remove_chars(current, 0, current->pos)) {
                refreshLine(current->prompt, current);
            }
            break;
        case ctrl('K'): /* Ctrl+k, delete from current to end of line, save deleted chars. */
            if (remove_chars(current, current->pos, current->chars - current->pos)) {
                refreshLine(current->prompt, current);
            }
            break;
        case ctrl('Y'): /* Ctrl+y, insert saved chars at current position */
            if (current->capture && insert_chars(current, current->pos, current->capture)) {
                refreshLine(current->prompt, current);
            }
            break;
        case ctrl('L'): /* Ctrl+L, clear screen */
            clearScreen(current);
            /* Force recalc of window size for serial terminals */
            current->cols = 0;
            refreshLine(current->prompt, current);
            break;
        default:
            /* Only tab is allowed without ^V */
            if (c == '\t' || c >= ' ') {
                if (insert_char(current, current->pos, c) == 1) {
                    refreshLine(current->prompt, current);
                }
            }
            break;
        }
    }
    return current->len;
}

int linenoiseColumns(void)
{
    struct current current;
    enableRawMode (&current);
    getWindowSize (&current);
    disableRawMode (&current);
    return current.cols;
}

char *linenoise(const char *prompt)
{
    int count;
    struct current current;
    char buf[LINENOISE_MAX_LINE];

    if (enableRawMode(&current) == -1) {
        printf("%s", prompt);
        fflush(stdout);
        if (fgets(buf, sizeof(buf), stdin) == NULL) {
            return NULL;
        }
        count = strlen(buf);
        if (count && buf[count-1] == '\n') {
            count--;
            buf[count] = '\0';
        }
    }
    else
    {
        current.buf = buf;
        current.bufmax = sizeof(buf);
        current.len = 0;
        current.chars = 0;
        current.pos = 0;
        current.prompt = prompt;
        current.capture = NULL;

        count = linenoiseEdit(&current);

        disableRawMode(&current);
        printf("\n");

        free(current.capture);
        if (count == -1) {
            return NULL;
        }
    }
    return strdup(buf);
}

/* Using a circular buffer is smarter, but a bit more complex to handle. */
int linenoiseHistoryAdd(const char *line) {
    char *linecopy;

    if (history_max_len == 0) return 0;
    if (history == NULL) {
        history = (char **)malloc(sizeof(char*)*history_max_len);
        if (history == NULL) return 0;
        memset(history,0,(sizeof(char*)*history_max_len));
    }

    /* do not insert duplicate lines into history */
    if (history_len > 0 && strcmp(line, history[history_len - 1]) == 0) {
        return 0;
    }

    linecopy = strdup(line);
    if (!linecopy) return 0;
    if (history_len == history_max_len) {
        free(history[0]);
        memmove(history,history+1,sizeof(char*)*(history_max_len-1));
        history_len--;
    }
    history[history_len] = linecopy;
    history_len++;
    return 1;
}

int linenoiseHistoryGetMaxLen(void) {
    return history_max_len;
}

int linenoiseHistorySetMaxLen(int len) {
    char **newHistory;

    if (len < 1) return 0;
    if (history) {
        int tocopy = history_len;

        newHistory = (char **)malloc(sizeof(char*)*len);
        if (newHistory == NULL) return 0;

        /* If we can't copy everything, free the elements we'll not use. */
        if (len < tocopy) {
            int j;

            for (j = 0; j < tocopy-len; j++) free(history[j]);
            tocopy = len;
        }
        memset(newHistory,0,sizeof(char*)*len);
        memcpy(newHistory,history+(history_len-tocopy), sizeof(char*)*tocopy);
        free(history);
        history = newHistory;
    }
    history_max_len = len;
    if (history_len > history_max_len)
        history_len = history_max_len;
    return 1;
}

/* Save the history in the specified file. On success 0 is returned
 * otherwise -1 is returned. */
int linenoiseHistorySave(const char *filename) {
    FILE *fp = fopen(filename,"w");
    int j;

    if (fp == NULL) return -1;
    for (j = 0; j < history_len; j++) {
        const char *str = history[j];
        /* Need to encode backslash, nl and cr */
        while (*str) {
            if (*str == '\\') {
                fputs("\\\\", fp);
            }
            else if (*str == '\n') {
                fputs("\\n", fp);
            }
            else if (*str == '\r') {
                fputs("\\r", fp);
            }
            else {
                fputc(*str, fp);
            }
            str++;
        }
        fputc('\n', fp);
    }

    fclose(fp);
    return 0;
}

/* Load the history from the specified file. If the file does not exist
 * zero is returned and no operation is performed.
 *
 * If the file exists and the operation succeeded 0 is returned, otherwise
 * on error -1 is returned. */
int linenoiseHistoryLoad(const char *filename) {
    FILE *fp = fopen(filename,"r");
    char buf[LINENOISE_MAX_LINE];

    if (fp == NULL) return -1;

    while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) {
        char *src, *dest;

        /* Decode backslash escaped values */
        for (src = dest = buf; *src; src++) {
            char ch = *src;

            if (ch == '\\') {
                src++;
                if (*src == 'n') {
                    ch = '\n';
                }
                else if (*src == 'r') {
                    ch = '\r';
                } else {
                    ch = *src;
                }
            }
            *dest++ = ch;
        }
        /* Remove trailing newline */
        if (dest != buf && (dest[-1] == '\n' || dest[-1] == '\r')) {
            dest--;
        }
        *dest = 0;

        linenoiseHistoryAdd(buf);
    }
    fclose(fp);
    return 0;
}

/* Provide access to the history buffer.
 *
 * If 'len' is not NULL, the length is stored in *len.
 */
char **linenoiseHistory(int *len) {
    if (len) {
        *len = history_len;
    }
    return history;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


















































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted bindings/s2/linenoise/linenoise.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/* linenoise.h -- guerrilla line editing library against the idea that a
 * line editing lib needs to be 20,000 lines of C code.
 *
 * See linenoise.c for more information.
 *
 * ------------------------------------------------------------------------
 *
 * Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
 * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *  *  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *  *  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef __LINENOISE_H
#define __LINENOISE_H

#ifndef NO_COMPLETION
typedef struct linenoiseCompletions {
  size_t len;
  char **cvec;
} linenoiseCompletions;

/*
 * The callback type for tab completion handlers.
 */
typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);

/*
 * Sets the current tab completion handler and returns the previous one, or NULL
 * if no prior one has been set.
 */
linenoiseCompletionCallback * linenoiseSetCompletionCallback(linenoiseCompletionCallback *);

/*
 * Adds a copy of the given string to the given completion list. The copy is owned
 * by the linenoiseCompletions object.
 */
void linenoiseAddCompletion(linenoiseCompletions *, const char *);
#endif

/*
 * Prompts for input using the given string as the input
 * prompt. Returns when the user has tapped ENTER or (on an empty
 * line) EOF (Ctrl-D on Unix, Ctrl-Z on Windows). Returns either
 * a copy of the entered string (for ENTER) or NULL (on EOF).  The
 * caller owns the returned string and must eventually free() it.
 */
char *linenoise(const char *prompt);

/*
 * Adds a copy of the given line of the command history.
 */
int linenoiseHistoryAdd(const char *line);

/*
 * Sets the maximum length of the command history, in lines.
 * If the history is currently longer, it will be trimmed,
 * retaining only the most recent entries. If len is 0 or less
 * then this function does nothing.
 */
int linenoiseHistorySetMaxLen(int len);

/*
 * Returns the current maximum length of the history, in lines.
 */
int linenoiseHistoryGetMaxLen(void);

/*
 * Saves the current contents of the history to the given file.
 * Returns 0 on success.
 */
int linenoiseHistorySave(const char *filename);

/*
 * Replaces the current history with the contents
 * of the given file.  Returns 0 on success.
 */
int linenoiseHistoryLoad(const char *filename);

/*
 * Frees all history entries, clearing the history.
 */
void linenoiseHistoryFree(void);

/*
 * Returns a pointer to the list of history entries, writing its
 * length to *len if len is not NULL. The memory is owned by linenoise
 * and must not be freed.
 */
char **linenoiseHistory(int *len);

/*
 * Returns the number of display columns in the current terminal.
 */
int linenoiseColumns(void);

#endif /* __LINENOISE_H */
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
















































































































































































































































Deleted bindings/s2/linenoise/utf8.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/**
 * UTF-8 utility functions
 *
 * (c) 2010 Steve Bennett <steveb@workware.net.au>
 *
 * See LICENCE for licence details.
 */

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "utf8.h"

#ifdef USE_UTF8
int utf8_fromunicode(char *p, unsigned short uc)
{
    if (uc <= 0x7f) {
        *p = uc;
        return 1;
    }
    else if (uc <= 0x7ff) {
        *p++ = 0xc0 | ((uc & 0x7c0) >> 6);
        *p = 0x80 | (uc & 0x3f);
        return 2;
    }
    else {
        *p++ = 0xe0 | ((uc & 0xf000) >> 12);
        *p++ = 0x80 | ((uc & 0xfc0) >> 6);
        *p = 0x80 | (uc & 0x3f);
        return 3;
    }
}

int utf8_charlen(int c)
{
    if ((c & 0x80) == 0) {
        return 1;
    }
    if ((c & 0xe0) == 0xc0) {
        return 2;
    }
    if ((c & 0xf0) == 0xe0) {
        return 3;
    }
    if ((c & 0xf8) == 0xf0) {
        return 4;
    }
    /* Invalid sequence */
    return -1;
}

int utf8_strlen(const char *str, int bytelen)
{
    int charlen = 0;
    if (bytelen < 0) {
        bytelen = strlen(str);
    }
    while (bytelen) {
        int c;
        int l = utf8_tounicode(str, &c);
        charlen++;
        str += l;
        bytelen -= l;
    }
    return charlen;
}

int utf8_index(const char *str, int index)
{
    const char *s = str;
    while (index--) {
        int c;
        s += utf8_tounicode(s, &c);
    }
    return s - str;
}

int utf8_charequal(const char *s1, const char *s2)
{
    int c1, c2;

    utf8_tounicode(s1, &c1);
    utf8_tounicode(s2, &c2);

    return c1 == c2;
}

int utf8_tounicode(const char *str, int *uc)
{
    unsigned const char *s = (unsigned const char *)str;

    if (s[0] < 0xc0) {
        *uc = s[0];
        return 1;
    }
    if (s[0] < 0xe0) {
        if ((s[1] & 0xc0) == 0x80) {
            *uc = ((s[0] & ~0xc0) << 6) | (s[1] & ~0x80);
            return 2;
        }
    }
    else if (s[0] < 0xf0) {
        if (((str[1] & 0xc0) == 0x80) && ((str[2] & 0xc0) == 0x80)) {
            *uc = ((s[0] & ~0xe0) << 12) | ((s[1] & ~0x80) << 6) | (s[2] & ~0x80);
            return 3;
        }
    }

    /* Invalid sequence, so just return the byte */
    *uc = *s;
    return 1;
}

#endif
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






































































































































































































































Deleted bindings/s2/linenoise/utf8.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#ifndef UTF8_UTIL_H
#define UTF8_UTIL_H
/**
 * UTF-8 utility functions
 *
 * (c) 2010 Steve Bennett <steveb@workware.net.au>
 *
 * See LICENCE for licence details.
 */

#ifndef USE_UTF8
#include <ctype.h>

/* No utf-8 support. 1 byte = 1 char */
#define utf8_strlen(S, B) ((B) < 0 ? (int)strlen(S) : (B))
#define utf8_tounicode(S, CP) (*(CP) = (unsigned char)*(S), 1)
#define utf8_index(C, I) (I)
#define utf8_charlen(C) 1

#else
/**
 * Converts the given unicode codepoint (0 - 0xffff) to utf-8
 * and stores the result at 'p'.
 * 
 * Returns the number of utf-8 characters (1-3).
 */
int utf8_fromunicode(char *p, unsigned short uc);

/**
 * Returns the length of the utf-8 sequence starting with 'c'.
 * 
 * Returns 1-4, or -1 if this is not a valid start byte.
 *
 * Note that charlen=4 is not supported by the rest of the API.
 */
int utf8_charlen(int c);

/**
 * Returns the number of characters in the utf-8 
 * string of the given byte length.
 *
 * Any bytes which are not part of an valid utf-8
 * sequence are treated as individual characters.
 *
 * The string *must* be null terminated.
 *
 * Does not support unicode code points > \uffff
 */
int utf8_strlen(const char *str, int bytelen);

/**
 * Returns the byte index of the given character in the utf-8 string.
 * 
 * The string *must* be null terminated.
 *
 * This will return the byte length of a utf-8 string
 * if given the char length.
 */
int utf8_index(const char *str, int charindex);

/**
 * Returns the unicode codepoint corresponding to the
 * utf-8 sequence 'str'.
 * 
 * Stores the result in *uc and returns the number of bytes
 * consumed.
 *
 * If 'str' is null terminated, then an invalid utf-8 sequence
 * at the end of the string will be returned as individual bytes.
 *
 * If it is not null terminated, the length *must* be checked first.
 *
 * Does not support unicode code points > \uffff
 */
int utf8_tounicode(const char *str, int *uc);

#endif

#endif
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






























































































































































Deleted bindings/s2/r-tester.sh.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/bin/bash
#HELP>#####################################################################
# Runs require.s2 unit test scripts.
#
# Usages:
#
# 1) $0 [flags]
# searches for *.test.s2 in $(dirname $0)/require.d and runs them.
#
# 2) $0 [flags] moduleName ...moduleNameN
# Expects that each argument is a require.s2 module name and
# expects moduleName.test.s2 to exist in the require.s2 module
# search path.
#
# Runs each test in its own s2sh instance and exits with non-0 if
# any test fails.
#
# Flags:
#
# -V enables VERBOSE mode, which simply outputs some extra output.
#
# -vg enables valgrind/massif tests IF valgrind is found in the path.
# These generate some files which one may peruse to collect allocation
# metrics.
#
# Any flags not handled by this script are passed on to the underlying
# s2sh call (quoting might be a problem - avoid complex flags).
#
#HELP<###################################################################

dir=$(dirname $0)
#[[ '.' = "${dir}" ]] && dir=$PWD
s2sh=$dir/f-s2sh

[[ -x "$s2sh" ]] || s2sh=$(which s2sh 2>/dev/null)
[[ -x "$s2sh" ]] || {
    echo "s2sh not found :(. Looked in [${dir}] and \$PATH." 1>&2
    exit 1
}
echo "s2sh = ${s2sh}"
rdir=${REQUIRES2_HOME}
[[ -d "$rdir" ]] || rdir=$dir/require.d
[[ -d "$rdir" ]] || {
    echo "Missing 'required' dir: $rdir" 1>&2
    exit 2
}
rdir=$(cd $rdir>/dev/null; echo $PWD)
export S2_HOME=$PWD # used by require.s2

requireCall="(s2.require ||| import('${rdir}/require.s2'))"

DO_VG=0
VERBOSE=0
S2SH_ADD_FLAGS="${S2SH_XFLAGS}"
DO_DEBUG=0

list=''
for i in "$@"; do
    case $i in
        -V) VERBOSE=1
            ;;
        -vg) DO_VG=1
            ;;
        -debug)
            DO_DEBUG=1
            ;;
        -\?|--help)
            echo "$0:"
            sed -n '/^#HELP/,/^#HELP/p' < $0 | grep -v -e '^#HELP' \
                | sed -e 's/^#/    /g'
            exit 0;
            ;;
        -*)
            S2SH_ADD_FLAGS="${S2SH_ADD_FLAGS} $i"
            ;;
        *) list="$list $i.test"
        ;;        
    esac
done

[[ x = "x${list}" ]] && {
    cd $rdir || exit $? # oh, come on, steve, this isn't C!
    list=$(find . -name '*.test.s2' | cut -c3- | sed -e 's/\.s2$//g' | sort)
    cd - >/dev/null
}

[[ "x" = "x${list}" ]] && {
    echo "Didn't find any *.test.s2 scripts :(" 1>&2
    exit 3
}
list=$(echo $list) # remove newlines
#echo "Unit test list: $list"

function verbose(){
    [[ x1 = "x${VERBOSE}" ]] && echo $@
}

function vgTest(){
    local test=$1
    shift
    local flags="$@"
    local tmp=tmp.$test
    local vgout=vg.$test
    cat <<EOF > $tmp
${requireCall}(['nocache!${test}']);
EOF
    local cmd="$vg --leak-check=full -v --show-reachable=yes --track-origins=yes $s2sh ${S2SHFLAGS} -f ${tmp} ${flags}"
    echo "Valgrind: $test"
    verbose -e "\t$cmd"
    $cmd &> $vgout || {
        rc=$?
        rm -f $tmp
        echo "Valgrind failed. Output is in ${vgout}"
        exit $rc
    }
    #rm -f $vgout
    echo "Valground: $test [==>$vgout]"
    vgout=massif.$test
    local msp=ms_print.$test
    cmd="$massif --massif-out-file=$msp $s2sh ${S2SHFLAGS} -f ${tmp} ${flags}"
    echo "Massifying: $test"
    verbose -e "\t$cmd"
    $cmd &> $vgout || {
        rc=$?
        rm -f $tmp
        echo "Massif failed. Output is in ${vgout}"
        exit $rc
    }
    echo "Massified $test: try: ms_print ${msp} | less"
}

#if [ -e f-s2sh ]; then
#    # kludge for the libfossil source tree
#    S2SH_ADD_FLAGS="${S2SH_ADD_FLAGS}"
#fi
if [[ x1 = x${DO_DEBUG} ]]; then
    s2sh="gdb --args $s2sh"
fi
S2SHFLAGS="-rv -si ${S2SH_ADD_FLAGS}"
# Reminder: some fsl modules rely on code set up by s2sh init script,
# so we cannot use the --a option in libfossil.
echo S2SHFLAGS=$S2SHFLAGS
for test in $list; do
    echo "Running require.s2 test: ${test}"
    outfile=${rdir}/${test}.test_out
    verbose "Output going to: $outfile"
    rm -f "$outfile"
    tmpfile=${rdir}/${test}.tmp
    echo "${requireCall}(['nocache!${test}']);" > $tmpfile
    cmd="$s2sh ${S2SHFLAGS} -o ${outfile} -f ${tmpfile}"
    echo "Running test [${test}]: $cmd"
    $cmd
    rc=$?
    [[ 0 -eq $rc ]] || {
        echo "Test '${test}' failed. Script output (if any) saved to [${outfile}]" 1>&2
        exit $rc
    }
    #echo "Test did not fail: ${test}"
    rm -f $tmpfile
    if [[ -s "$outfile" ]]; then
        verbose -e "\tOutput is in: ${outfile}"
    else
        rm -f "${outfile}"
    fi
done

if [[ x1 = "x$DO_VG" ]]; then
   vg=$(which valgrind)
    if [[ -x "$vg" ]]; then
        echo "Runing test(s) through valgrind..."
        massif="${vg} --tool=massif --time-unit=ms --heap-admin=0"
        for test in $list; do
            outfile=$rdir/${test}.test_out
            rm -f "$outfile"
            vgTest $test -o "${outfile}"
        done
    fi
fi

echo "Done! Tests run: ${list}"
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<








































































































































































































































































































































































Deleted bindings/s2/require-demo.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// Alias our requirejs workalike for convenience...
assert Fossil.require /* gets set via f-s2sh.s2 */;
const R = Fossil.require;
if(!R.fsl){
    /* kludge to avoid requiring a CLI flag to provide a repo */
    const f = R(['fsl/context']).0;
    affirm f === R.fsl;
    affirm R.fsl.db;
    R.fsl.openCheckout();
}/* else a downstream module will open the repo as needed */


print('require() plugins:', R.plugins.propertyKeys());

// Use it like requirejs:
R([// Resources to load: those with a '!' are "plugins", not script files
    'fsl/wikiByName!download',
    'text!require-demo.s2',
    'fsl/blob!rid:1',
    'fsl/manifest!trunk'
  ], // Callback to pass the resources to:
  function(page, thisScript, rid1, trunk){
      print(__FLC, typeinfo(name page), typeinfo(name thisScript), typeinfo(name rid1));
      print("Wiki page [", page.L,"] is", page.W.lengthBytes(),"bytes long.");
      print("This script is",thisScript.lengthBytes(),"bytes long.");
      print("RID 1's manifest is", rid1.length(), "bytes long.");
      print("trunk has", trunk.F.length(), "F-cards.");
  });


R(['fsl/context', // shared/cached Fossil.Context instance
   'fsl/wiki/util' // various Wiki utilities
  ],
  function(fsl, wikiUtil){
      var pages = [];
      wikiUtil.getPageNames().
          eachIndex(proc(name){
              pages[] = fsl.loadManifest( wikiUtil.getLatestRid(name) );
          });
      print(pages.length(),"wiki pages found:");
      const j2h = Fossil.time.julianToHuman;
      0 && pages.sort(function(l,r){
          // It seems we have a bug in/around cwal_array_sort_stateful(), as
          // the sorted result here is very unexpected. But i'm too tired
          // to fight it.
          // print('',j2h(r.D), '\n', j2h(l.D), j2h(r.D).compare(j2h(l.D)));
          //return j2h(r.D).compare(j2h(l.D));
          //print(r.D, l.D, r.D.compare(l.D));
          //return (r.D * 10000000).toInt() - (l.D * 10000000).toInt();
          return r.D.compare(l.D);
      });
      pages.eachIndex(proc(page){
          print( "    %1$-25s @ %2$s by %3$s %4$d bytes".
                 applyFormat(page.L,
                             j2h(page.D),
                             page.U,
                             page.W.lengthBytes()));
      });
      print("");
  });

R(['fsl/timeline/basic', // list of most recent rows from the event table
   'ostream' // output utility for fans of C++
  ],
  proc(tl, os){
      os << "Most recent timeline entries:\n";
      tl.eachIndex(proc(v){
          os << v.type << ' ' << v.uuid.substr(0,10)
              << ' @ ' << Fossil.time.julianToHuman(v.mtime)
              << ' by '
              << (v.euser ||| v.user)
              << '\n\t'
              << (v.ecomment ||| v.comment)
              << '\n';
      });
      os << '\n';
  });

// An auto-loaded (not pre-registered) plugin:
R(['demo!foo',
   'demo!bar?a=1&b&c=hi there'
  ],
  proc(demo, d2){
      print("Demo auto-loaded plugin:",demo);
      assert d2 === demo /* because of how this particular plugin works */;
      assert 2 === demo.counter;
      assert 'bar' === demo.lastArgs.0;
      assert 'hi there' === demo.lastArgs.1.c;
  });

if(Fossil.file.isFile('cgimod.so')){
    R(['dll!cgimod', // the CGI API (loadable module)
       'dll!cgimod', // same instance (cached)
       'dll!cgimod?checkingCache' // params bypass caching
       /* reminder: 2 instances of this module is semantically
          invalid in a real CGI context. We're just testing that
          R()'s docs regarding caching matches its behaviour. */
      ],
      proc(cgi, cgi2, cgi3){
          assert cgi === cgi2;
          assert cgi !== cgi3;
             print("Loaded cgi module: "+cgi);
      });
}else{
    print("cgimod DLL not found.");
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




















































































































































































































Deleted bindings/s2/require.d/BufferFactory.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
   A require.s2 module which returns an object containing "factory
   methods" for creating Buffers.
*/
return {
    /**
       Usage:

       var buffer = thisObj.new([reservedMemorySize=0]);
    */
    new: s2.Buffer.new,
    /**
       Usage:

       var buffer = thisObj.readFile(filename);
    */
    readFile: s2.Buffer.readFile
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




































Deleted bindings/s2/require.d/BufferFactory.test.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
   A require.s2 module which returns an object containing "factory
   methods" for creating Buffers.
*/
requireS2(
['BufferFactory'],
proc(bfac){
    var b = bfac.new(100);
    affirm b.capacity()>=100;
    
    b = bfac.readFile(__FILE);
    affirm 'buffer' === typename b;
    affirm b.length()>50;
});
<
<
<
<
<
<
<
<
<
<
<
<
<
<




























Deleted bindings/s2/require.d/DataModels/TestModel.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
   Test/demo require.s2 DataModel module the modeler module
   and/or the DataModel plugin.
*/
return {
    //__typename: 'Testing that this will not get overwritten',
    /**
       Gets called by the modeler framework when this module's
       (inherited) new() method is called. In the context of this
       function, "this" will be the new Model instance, which inherits
       this object.
       
       The return value is ignored by the modeler framework.
    */
    initialize: proc(a,b,c){
        print(__FLC, 'initialize()ing', typename this, this);
        print(__FLC, 'argv =', argv);
        print(__FLC, 'super =',super);
        assert __FILE.indexOf(typename this)>=0
        /* __typename gets automatically set from the file name
           when the model is loaded if it does not set one itself. */;
        this.attr('a',a)
            .attr('b',b)
            .attr('c',c);
    },
    /**
       Attributes defined in the prototype are visible via the
       inherited attr() method, but do not get serialized via the
       inherited toJSON() method unless their values have been
       explicitly set in the most-derived model instance.
    */
    attributes:{
        foo: -1
    }
};
/*.withThis(proc(){
    return ('undefined' === typename requireS2)
        ? this
        : ((const that=this),
           requireS2(['modeler'],proc(M){
               return M.extendModel(that)
           }));
});*/
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






















































































Deleted bindings/s2/require.d/Ticker.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
/**
   A require.s2 module implementing an abstract timer class. See the
   inlined docs (down below) for details.

   Example usage:


   const Ticker = require(['Ticker']).0;
   const t = new Ticker();
   t.addEvent(1, true, proc(){print("repeating event")});
   t.addEvent(3, proc(){print("one-time event")});

   t.tick();
   t.tick();
   t.tick();
   // or: t.tick(3)

   Will output the following, though the order of events at firing at
   the same logical time (the last two lines here) is not defined and
   may change during the lifetime of repeating events or differ
   depending on whether the clock is incremented one tick at a time or
   more than one:

   repeating event
   repeating event
   one-time event
   repeating event
*/
/**
   Ticker is a utility class implementing an abstract
   timer. It doesn't know anything about time - it keeps
   track of time in abstract ticks. Events can be added
   to it which are fired after a certain number of ticks,
   optionally repeating every N ticks.
    
   To create a new instance, call this function. Its return
   value inherits this function.

   It's functionally similar to JavaScript's setTimeout()
   and setInterval() except for:

   a) It is strictly synchronous.

   b) It has no clock. The client has to tell it that X amount of
   (abstract) time has passed.

   c) It currently has no way to properly remove events (and doing so
   from an event handler may mess up its iteration for the time being).
*/


return {
    __typename: 'Ticker',
    /** Internal list of events */
    //tlist: [],

    /** Current timestamp (tick counter) */
    //ts: 0,

    /** Internal helper to sort event entries by their timestamp.
        Empty/null entries are sorted as less than any others
        because this make them easy to remove.
    */
    sortEvents:function f(){
        f.sorter || (f.sorter=function(l,r){
            // sort nulls to the LEFT (easier (but more memmov'ing) to lop them off that way)
            l || return r ? -1 : 0;
            r || return 1;
            return compare(l.when, r.when) |||
                compare(l.priority, r.priority) |||
                compare(l.ts,r.ts) |||
                compare(l.id,r.id);
        });
        this.tlist.sort(f.sorter);
    }.importSymbols({compare: function(l,r){
        return (l<r) ? -1 : ((l>r) ? 1 : 0);
	}}),

    /**
       Returns an incremental integer value on each call.
    */
    nextEventId: function(){
        this.idCounter || (this.idCounter = 0);
        return ++this.idCounter;
    },
    
    /**
       Adds a timer event to (potentially) be triggered
       via advancement of the timer (see tick()).

       'when' is the relative tick number (in the future) in which
       to fire the event. Must be a positive value.

       'repeats' is a boolean. Repeating events trigger every 'when'
       ticks.

       'what' is a Function which gets called when the event timer
       is triggered. When called, what's 'this' will be the
       event Object containing 'what'.

       If called with two arguments then it is treated as if
       (when, false, what) are passed in.

       If called with one argument then it is equivalent to calling
       addEvent2().
    
       Returns an Object describing the event. Clients may add
       their own properties to it and use those from the
       what() handler (accessible via this.PROPERTY).

       Callers can control the ordering of events fired at
       the same time by setting a priority numeric property
       on the returned event object. Priority sorts using
       normal numeric comparison, so lower values sort first.
       The default priority is 0 and negative values are legal.

       This class supports the following event object properties
       (and ignores all others):

       id: has no meaning but is used as a fallback option when sorting.
       Clients may set this to what they wish, and a default value
       (incremental integers) is set.

       ts: the current "timestamp", in ticks, of this ticker. It is
       incremented by calling tick().

       when: the tick time at which the event will next fire.

       what: the function to call when the event fires.

       interval: an integer specifying that the event should repeat
       every this-many ticks. If not set, or set to a falsy value,
       the event is a one-time event and will be removed after firing
       by the tick() process.

       priority: an integer value which determines run order for
       events firing at the same tick (lower values sort first). For
       "overlapping" ticks (with a time span of more than one tick)
       this order might be somewhat intuitive: all "overlap" runs of
       a given event handler are processed before the next
       event. i.e. the priority order is maintained, but each event
       may be run multiple times in succession before another event
       with a higher (or the same) priority value.

       client: this property name is reserved solely for use by
       client code. This API promises never to use that property
       key in event objects.
    */
    addEvent:function(when, repeats, what ){
        const argc = argv.length();
        (1===argc) && return this.addEvent2(when);
        if(2===argc && typeinfo(isfunction repeats)){
            what = repeats;
            repeats = false;
        }
        affirm when>0;
        affirm typeinfo(isfunction what);
        this.idCounter || (this.idCounter = 0);
        const ev = {
            id: this.nextEventId(),
            ts: this.ts,
            when: this.ts + when,
            what: what,
            priority: 0
        };
        repeats && (ev.interval = when);
        this.tlist.push(ev);
        return ev;
    },

    /**
       An alternate form of addEvent() which takes an object.

       Returns the object passed to it, after enriching it
       with event state and adding it to the list.

       If the object has a an 'interval' property but no 'when'
       property then its 'when' is set to the interval value.
       Likewise, if 'when' is set and 'repeats' is set to a truthy
       value, 'interval' gets set to 'when'.

       Example:

       ticker.addEvent({interval: 3, what:myFunc});

       will set up a repeating event, firing first in 3 ticks,
       and then ever 3 ticks after that.
    */
    addEvent2:function(obj){
        affirm typeinfo(iscontainer obj);
        affirm typeinfo(isfunction obj.what);
        if(obj.repeats && !obj.interval){
            obj.interval = obj.when;
        }
        unset obj.repeats;
        if(obj.interval && !obj.when){
            obj.when = obj.interval;
        }
        affirm obj.when > 0;
        obj.ts = this.ts;
        obj.priority || (obj.priority = 0);
        obj.id || (obj.id = this.nextEventId());
        this.tlist[] = obj;
        return obj;
    },

    /**
       Removes all event handlers and resets the tick counter to 0.
    */
    reset: function(){
        this.ts = 0;
        this.tlist.clear();
        return this;
    },

    /**
       Increments the tick count by incr (default=1) and triggers any
       events whose time comes. If incr is greater than one and a
       repeating event would normally have been triggered multiple
       times within that span, this function calls it the number of
       times it would have been called had tick() been called in
       increments of 1. The order of event callbacks is unspecified
       by default - they continually get re-sorted based on their
       trigger time. Clients can control the order of like-timed
       events by setting a priority level on an event - see
       addEvent() for details.

       Events added to this object by an event handler will NOT
       be called in this invocation of tick() - they are queued up
       and added to the event list after tick() has finished processing
       any pending handlers.

       Returns this object.
    */
    tick: function(incr=1){
        affirm typeinfo(isinteger incr);
        affirm incr >= 0; // a tick value of 0 might be interesting for something
        const li = this.tlist;
        var n = li.length();
        affirm typeinfo(isarray li);
        this.ts += incr;
        n || return this;

        /* Remove any expired-and-fired events here (as opposed to afterwards)
           because (it turns out) this simplifies things. */
        this.sortEvents(); // moves nulls to the left
        while(n && !li.0){
            /* Remove any expired events */
            li.shift();
            --n;
        }
        n || return this;

        // Move this.tlist so that adding events while looping does not affect us.        
        const listSentry = this.tlist;
        this.tlist = [];

        /*
          Loop over events until we find one with a 'when'
          set in the future. All current events will be to the
          left of that one.
        */
        const tm = this.ts;
        for(var i = 0, e, shiftCount = 0, repeater = 0;
            i<n; ++i){

            e = repeater ||| li[i];
            repeater = 0;
            if(e.when>tm){ break } /* first in-the-future event. */
            else {
                affirm e.when > e.ts;
                if(var err=catch{e.what()}){
                    print(__FLC,"WARNING: tick handler threw. Removing it.",e,err);
                    unset e.interval;
                }
                if(e.interval){ /* Set this one up to run again */
                    e.ts = e.ts + e.interval;
                    e.when = e.ts + e.interval;
                    if(e.when <= tm){
                        /* timespan skipped one or more intervals. Run them now. */
                        //print(__FLC,"firing again for overlap:",e);
                        --i /* fudge the loop counter to repeat this entry.
                               May break this.tlist is modified by
                               callback. */;
                        repeater = e;
                        continue;
                    }
                }else{ /* remove the event */
                    li[i] = undefined;
                    ++shiftCount;
                }
            }
        }
        /* Move this.tlist back... */
        if(this.tlist.isEmpty()){ /* no changes made during iteration */
            this.tlist = listSentry;
        }else{
            /* Events added while looping. Integrate them now.
               We swap tlist here so that we can keep the original
               array (as a minor potential allocation optimization). */
            this.tlist.eachIndex(integrateTlist);
            this.tlist = listSentry;
        }
        return this;
    }/*tick()*/.importSymbols({
        integrateTlist: proc(i,v){ listSentry.push(v) }
    }),
    /**
       Constructor function for new instances.
    */
    __new: proc(){
        this.tlist = [];
        this.ts = 0;
    }
};
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






















































































































































































































































































































































































































































































































































































































































Deleted bindings/s2/require.d/Ticker.test.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
requireS2(['time','nocache!Ticker'], proc(time, Ticker){
    affirm 'Ticker' === typeinfo(name Ticker);
    const t = new Ticker();
    affirm t inherits Ticker;
    affirm 'Ticker' === typeinfo(name t);
    const callback = proc(){print('fired:',this.name)};
    var ev;
    affirm typeinfo(isfunction callback);
    // Repeating event, every 3rd tick:
    ev = t.addEvent(3, true, callback);

    ev.name = "threeer";
    ev.priority = 3;
    /*
      Event priority is used to break ties for events firing at
      the same virtual time. It sorts by value, so a lower value
      has a higher priority. Events have a default priority of 0.
    */

    // Add a repeating event which fires every tick:
    ev = t.addEvent(1, true, callback);
    ev.name = "oner";
    ev.priority = -1;


    // Add a one-time event, fired once, four ticks from now:
    affirm typeinfo(isfunction callback);
    ev = t.addEvent(4, callback);
    ev.name = "fourer";
    ev.priority = 4;

    // Add a repeating event, firing every 2nd tick:
    ev = t.addEvent({interval: 2, what: callback, name: "twoer"});

    print(__FLC,typeinfo(name t),':',t);

    // Now advance our virtual clock a few times...
    var i = 1, max = 10, sleepTimeMs = 100;

    print("Ticking",max,"times, sleeping",sleepTimeMs," millis between ticks...");
    //const timeFmt = {%Y-%m-%d %H:%M:%S}
    //const startTime = strftime(timeFmt, time())
    for( ;i<=max; i=i+1){
        print("TICK #"+i);
        time.mssleep(sleepTimeMs);
        t.tick();
    }
    //const endTime = strftime(timeFmt, time());
    print('After loop: Tick =',t.ts);
    //print('Start time:',startTime,'\nEnd time  :',endTime);
    if(0) {
        print('"Manually" ticking...');
        t.tick(6); // note how priority sorting kinda breaks down here
        // potential workaround: run ticks>1 in a loop of smaller ticks,
        // but how small? Maybe set tickerInstance.timeUnit=integer to
        // set the smallest time unit, and loop in increments of that unit?
    }
    //t.tick(5);
    //print(__FLC,"Done:",t);

    //dumpInternedStrings();
    t.reset();
    affirm t.tlist.isEmpty();
});
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
































































































































Deleted bindings/s2/require.d/cliargs.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/**
   A require.s2 module which returns a utility object for working with
   the CLI arguments provided via s2.ARGV.

   "Script arguments" are those passes to s2sh after any '--' flag.
   Those get imported into s2.ARGV (which is not set if no script
   flags are provided).

   s2.ARGV, if set, is an Array containing all flags passed after
   '--', plus possibly containing these two properties:

   .flags: an object of --flag=value pairs, where --flags with no
   value are treated as boolean true.

   .nonFlags: any script argument which does not start with '-' is
   assumed to be a filename or some other string, and is appended
   to this array.

   Either property will be undefined if no flags resp. non-flags
   are provided.
*/
return {

    /**
       Holds an Array of all arguments passed after '--' to s2sh
       resp. any app which installs that particular binding. It is
       undefined (not an empty array) if there are no such flags.
    */
    args: s2.ARGV,
    /**
       An object (or not) containing any -flags. The keys are stripped
       of any number of leading dashes and if a given flag is
       duplicated, the last one currently wins (as opposed to getting
       an array of values, though that might be a useful addition).
    */
    flags: s2.ARGV ? s2.ARGV.flags : undefined,

    /**
       An array (or not) of any non-flags passed after --,
       in the order they were passed in.
    */
    nonFlags: s2.ARGV ? s2.ARGV.nonFlags : undefined,

    /**
       If the given flag (minus any number of prefixing "-") was passed in
       the "script flags" (any flags passed after '--' to the s2sh
       interpreter), its value is returned, otherwise dflt is returned.
    */
    getFlag: proc(flag, dflt){
        return this.flags
            ? (this.flags[flag] ?: dflt)
            : dflt;
    },

    /**
       If the given flag (minus any number of prefixing "-") was
       passed in the "script flags" (any flags passed after '--' to
       the s2sh interpreter), its is removed from the flags and its
       value is returned, otherwise dflt is returned.

       When the last flag is removed, this.flags is unset.
    */
    takeFlag: proc(flag, dflt){
        this.flags || return dflt;
        if(undefined !== const v = this.flags[flag]){
            unset this.flags[flag];
            this.flags.# || unset this.flags;
            return rc;
        }else return dflt;
    },

    /**
       Returns true if the CLI flags (still) contain any flags,
       otherwise false.
    */
    hasFlags: proc(){
        return this.flags ? this.flags.#>0 : false;
    },

    /**
       Returns true if the CLI flags (still) contain any non-flags,
       otherwise false.
    */
    hasNonFlags: proc(){
        return this.nonFlags ? !this.nonFlags.isEmpty() : false;
    },

    /**
       Removes the first non-flag from the list and returns
       it. Returns undefined if there are no non-flags (or none
       remaining).

       When the last entry is removed, this.nonFlags is unset.
    */
    nextNonFlag: proc(){
        this.nonFlags || return undefined;
        var rc = this.nonFlags.shift();
        (0===this.nonFlags.length()) && unset this.nonFlags;
        return rc;
    }
};
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










































































































































































































Deleted bindings/s2/require.d/fsl/context.s2.

1
2
3
4
5
6
7
/**
   require() plugin which simply resolves to the shared Fossil.Context
   instance set up by the f-s2sh bootstrap code. This instance should
   be used by all other modules so that everybody is using the same
   database handles.
*/
return requireS2.fsl ||| (requireS2.fsl = new Fossil.Context());
<
<
<
<
<
<
<














Deleted bindings/s2/require.d/fsl/db/checkout.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
   require() plugin which returns a handle to the repo Db object for
   Fossil.require's shared Fossil.Context.  If no checkout has been
   opened when this is called, openCheckout() is called on the
   context, otherwise there are no side effects.
*/
return requireS2(['fsl/context'], proc(F){
    affirm const d = F.db;
    if(!d.checkout){
        F.openCheckout();
    }
    affirm d.repo;
    return d.repo;
});
<
<
<
<
<
<
<
<
<
<
<
<
<
<




























Deleted bindings/s2/require.d/fsl/db/config.s2.

1
2
3
4
5
6
7
8
9
10
11
/* require() plugin which returns a handle to the repo Db object for
   Fossil.require's shared Fossil.Context.  If no repo has been opened
   when this is called, it opens the repo specified via the
   --repo-db|-R=FILENAME CLI script flag.
*/
return requireS2(['fsl/context'], proc(F,args){
    affirm const d = F.db;
    d.config || F.openConfig();
    affirm d.config;
    return d.config;
});
<
<
<
<
<
<
<
<
<
<
<






















Deleted bindings/s2/require.d/fsl/db/main.s2.

1
2
3
4
5
/**
   require.s2 module which returns the "main" db handle of
   the shared Fossil.Context instance.
*/
return requireS2(['fsl/context']).0.db;
<
<
<
<
<










Deleted bindings/s2/require.d/fsl/db/repo.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* require() plugin which returns a handle to the repo Db object for
   Fossil.require's shared Fossil.Context.  If no repo has been opened
   when this is called, it opens the repo specified via the
   --repo-db|-R=FILENAME CLI script flag.
*/
return requireS2(['fsl/context','cliargs'], proc(F,args){
    affirm const d = F.db;
    if(!d.repo){
        const f = args.takeFlag('repo-db',args.takeFlag('R'))
            ||| throw "No repo db specified. "+
            "Pass the --repo-db|-R=filename "+
            "SCRIPT flag (after --) to the interpreter or open a checkout.";
        ('string' === typename f) || throw "Invalid argument type for the --repo-db|-R=DBFILE flag";
        F.openRepo(f);
    }
    affirm d.repo;
    return d.repo;
});
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




































Deleted bindings/s2/require.d/fsl/db/repoOrCheckout.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
   require.s2 module which returns either the repo db (specified by
   the -R|--repo-db=DBFILE script flags) or (if that fails) the repo
   db associated with the current checkout. Throws if it can neither
   open a checkout nor figure out the db from the CLI args.
*/
return requireS2(['fsl/context'], proc(F){
    affirm F.db;
    F.db.repo && return F.db.repo;
    F.db.checkout && return F.db.checkout;
    catch return requireS2(['fsl/db/repo']).0; // fails if no -R|--repo-db=DBFILE flag specified
    requireS2(['fsl/db/checkout']) /* opens the checkout */;
    assert F.db.checkout;
    return F.db.repo;
});

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
































Deleted bindings/s2/require.d/fsl/extendFossil.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/*
  A require.s2 module which adds some functionality
  to the various Fossil classes.

  This module is not intended to be used more than once and should not
  be cached. Thus, to use it:

  Fossil.require(['nocache!fsl/extendFossil'],proc(){})
*/


assert Fossil;
assert Fossil.Context;
Fossil.artifactTypes && return Fossil /* already ran this module */;


//Fossil.loadModule = s2.loadModule2;
//Fossil.importScript = s2.import2;

/**
  Counterpart of the C-level fsl_catype_e enum.
*/
Fossil.artifactTypes = {
    ANY: 0,
    CHECKIN: 1,
    CLUSTER: 2,
    CONTROL: 3,
    WIKI: 4,
    TICKET: 5,
    ATTACHMENT: 6,
    EVENT: 7
};

/**
    Fetches the first row from the given SQL statement (String or
    Buffer) and returns it as an Object (if asArray is false) or an
    Array (if asArray is true).

    If the bind argument is passed in then it is passed on to the
    Stmt.bind() method of the underlying statement. Use an array to
    bind multiple values. To specify the third parameter when there is
    nothing to bind, pass the undefined value as the second argument.
    i.e. bind===undefined is treated as "nothing to bind," instead of
    binding undefined/null.

    Throws on error. Returns undefined if no row is found.
*/
Fossil.Db.selectRow = proc(sql, bind = undefined, asArray = false){
    affirm this inherits Fossil.Db;
    const st = this.prepare(sql);
    var rc;
    const ex = catch {
        st.bind(bind);
        rc = asArray ? st.stepArray() : st.stepObject();
    };
    st.finalize();
    ex ? throw ex : return rc;
};

/**
   Returns an array containing the complete results of each row in the
   given SQL's result set. If asArray is true, each row is returned as
   an array of column values in the same order as the result set's,
   else each is returned an an Object of column name/value pairs with
   columns in an an unspecified order.

   The bind parameter may hold value(s) to bind to the given SQL.
*/
Fossil.Db.selectAll = proc(sql, bind = undefined, asArray = true){
    affirm this inherits Fossil.Db;
    const rc = [];
    this.each({
        sql: sql,
        bind: bind,
        mode: asArray ? 1 : 0,
        callback: 'rc[] = this',
    });
    return rc;
};

/**
    Given a SELECT-style query and optional bind parameters
    (either a single value for a single param or an array
    of multiple params), this routine simply dumps out
    the results of the query. bind===undefined is treated
    as "nothing to bind," instead of binding undefined/null.
*/
Fossil.Db.dumpQuery = proc(sql,bind = undefined, separator='\t'){
    affirm this inherits Fossil.Db;
    return this.each({
        sql:sql,
        bind:bind, // note that undefined value is treated as non-existent here
        callback:proc(){
            (1===rowNumber) && print(columnNames.join(separator));
            print(this.join(separator));
        }
    });
};

/**
    Stmt.each() loops over this.step(), calling func(N) on each
    iteration, where N is the current row number (1-based).  If func()
    returns a literal false, looping stops without an error. In the
    context of the call, 'this' is the Stmt object.

    Returns this object.
*/
Fossil.Db.Stmt.each = proc(func){
    affirm this inherits Fossil.Db.Stmt;
    affirm typeinfo(iscallable func) && typeinfo(iscallable func.call);
    for(var rowNum = 1; this.step(); ++rowNum){
        (false === func.call(this, rowNum)) && break;
        //^^^^^^^^ emulate Db.each()
    }
    return this;
};


/**
    Changes to the given dir and pushes the (old) current dir
    to the directory stack. To change back to the pre-pushd()
    directory, call Fossil.file.popd(). This function relies
    on this 'this' object beeing Fossil.file. Throws if it
    cannot change directories.

    The array of pushed directory names is available after calling
    this function one time via the property
    Fossil.file.pushd.dirStack. The most recent directory is at the
    end of that array. If that property is undefined, pushd() has
    never been called (or someone removed the property). If it is an
    empty array, there are currently no directories in the stack.
*/
Fossil.file.pushd = proc callee(dir){
    affirm 'string' === typename dir;
    const curdir = this.currentDir();
    this.chdir(dir);
    (callee.dirStack ||| (callee.dirStack = [])).push( curdir );
};

/**
    Pops the directory mostly recently pushed by Fossil.file.pushd()
    off of the directory stack and changes to that directory. Throws
    if called when no directories can be popped.
*/
Fossil.file.popd = proc(){
    const list = this.pushd.dirStack;
    const len = (list ? list.length() : 0)
        ||| throw "Directory stack is empty.";
    this.chdir(list[len-1]);
    return list.pop()
};

return Fossil;
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


















































































































































































































































































































Deleted bindings/s2/require.d/fsl/reports/common.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/**
   INCOMPLETE helper module for pending timeline/reporting bits.
*/
return requireS2(['fsl/db/repo'],
proc(db){
    const mod = {
        db: db,

        /*eventTypeMap:{
            '*': 'all',
            ci: 'checkins',
            w: 'wiki',
            g: 'tags',
            e: 'events',
            t: 'tickets'
        },*/

        /**
           Returns an array of years (integers) representing
           the years for which there is timeline activity in
           this repo. The entries are sorted ascending.
        */
        getActiveYears:proc(){
            return this.db.selectValues(<<<EOSQL
            SELECT DISTINCT CAST(strftime('%Y',mtime) AS INT) AS t
            FROM /*v_reports*/ event GROUP BY t ORDER BY t EOSQL);
        },

        /**
           Given a year in string or integer form, this function
           returns an array of integers holding the calendar week
           numbers (as determined by sqlite) of all "active" weeks
           during the give year. Returns an empty array if there is
           no timeline activity for that year.
         */
        getActiveWeeksForYear: proc(year){
            year || throw "Expecting a year (string|integer) value.";
            return this.db.selectValues(<<<EOSQL
                SELECT DISTINCT CAST(strftime('%W',mtime) AS INT) AS t
                FROM /*v_reports*/ event
                WHERE strftime('%Y',mtime)=?
                GROUP BY t ORDER BY t
                EOSQL,
                ''+year/*has to be a string for comparison to work*/);
        },

        /**
           Returns an array of objects, each with the properties
           (year, weeks), with year being the year (integer) for that
           entry and weeks being an array of the calendar weeks
           (integers, as determined by sqlite) which had activity
           during that year.
         */
        getActiveYearsAndWeeks: proc(){
            const rc = [], that = this;
            this.getActiveYears().eachIndex(proc(v){
                rc[] = {
                    year: v,
                    weeks: that.getActiveWeeksForYear(v)
                };
            });
            return rc;
        }
    };

    return mod;
});
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






































































































































Deleted bindings/s2/require.d/fsl/reports/common.test.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
requireS2([
    'fsl/db/repoOrCheckout',
    'fsl/util/repo',
    'fsl/reports/common'],
proc(ignored, rutil, rcom){
    affirm rcom.db;

    var x = rcom.getActiveYears();
    affirm 'array' === typename x;
    affirm x.length() > 0;

    const isLibfossil = 'libfossil' === rutil.getConfig('project-name');

    if(isLibfossil){
        affirm x.indexOf(2014) >= 0;
        affirm x.indexOf(1972) < 0;
    }

    x = rcom.getActiveWeeksForYear(2014);
    affirm 'array' === typename x;
    affirm x.length() > 0;

    if(isLibfossil){
        affirm 1 === x.0;
        affirm x.indexOf(22) < 0 /* no activity that week */;
        affirm x.indexOf(41) > 0 /* when this test was added */;
    }

    x = rcom.getActiveYearsAndWeeks();
    affirm 'array' === typename x;
    affirm x.length() > 0;
    affirm 'integer' === typename x.0.year;
    affirm 'array' === typename x.0.weeks;

});
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






































































Deleted bindings/s2/require.d/fsl/timeline/basic.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
   require() module which returns an array of Objects from the event
   table. Each includes all fields from the event table plus the
   associated blob's uuid.
*/
return requireS2(
    ['fsl/db/repo'],
    proc(repo){
        var rc = [];
        repo.each({
            mode: 0,
            sql:<<<_SQL
            SELECT e.*, b.uuid uuid
            FROM event e JOIN blob b ON e.objid=b.rid
            ORDER BY e.mtime DESC LIMIT 5
            _SQL,
            callback:'rc[] = this'
        });
        return rc;
});
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<








































Deleted bindings/s2/require.d/fsl/util/repo.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
   In-progress set of utilities for working with repo db tables.
*/
requireS2(['fsl/db/repoOrCheckout'], proc(repo){
    return {
        db: repo,
        /**
           Fetches an array of strings from this.db, each one being
           the name of a user in the current repository. They are
           sorted by name.
        */
        getUserList: proc(){
            return this.db.selectValues('select login from user order by login');
        },

        /**
           Returns the value of the repoDb.config field with the given key,
           or undefined if no such row is found.
        */
        getConfig: proc(key){
            affirm 'string' === typename key /* expecting a legal repo.config.key value */;
            return this.db.selectValue('SELECT value FROM config WHERE name=?',key);
        }
    };
});
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


















































Deleted bindings/s2/require.d/fsl/util/repo.test.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
   In-progress set of utilities for working with repo db tables.
*/
requireS2(['fsl/db/checkout'/*force repo db open*/,'fsl/util/repo'],
proc(ignored, repo){
    const db = repo.db;
    affirm db;
    affirm repo.getUserList;
    var x = repo.getUserList();
    affirm 'array' === typename x;
    affirm x.length() > 0;
    affirm x.indexOf('anonymous') >= 0;

    x = repo.getConfig('project-name');
    affirm 'string' === typename x;
    x = repo.getConfig('no-such-key');
    affirm undefined === x;
    affirm catch{repo.getConfig()}.message.indexOf("'string'") >0;
});
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






































Deleted bindings/s2/require.d/fsl/wiki/pageNames.s2.

1
2
3
4
return requireS2(
    ['fsl/wiki/util'],
    proc(util){ return util.getPageNames(); }
);
<
<
<
<








Deleted bindings/s2/require.d/fsl/wiki/util.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
   A set of wiki-related utilities.
*/
return requireS2(
    ['fsl/context', 'fsl/db/repo'],
    proc(fsl, repo){
        return {
            getPageNames: proc( caseSensitive ){
                const st = repo.prepare(<<<_SQL
                                        select distinct(substr(tagname,6)) name from tag t,
                                        tagxref x where x.tagid=t.tagid and t.tagname
                                        like 'wiki-%' order by name _SQL
                                        + (caseSensitive
                                           ? '' : ' collate nocase'));
                var rc = [];
                const ex = catch {
                    while(st.step()) rc[] = st.get(0);
                };
                st.finalize();
                ex ? throw ex : return rc;
            }.importSymbols(nameof repo),
            
            getLatestRid: proc(pageName){
                affirm pageName && 'string' === typename pageName;
                return repo.selectValue(<<<_SQL
                                        SELECT x.rid FROM tag t, tagxref x 
                                        WHERE x.tagid=t.tagid
                                        AND t.tagname=?
                                        ORDER BY mtime DESC LIMIT 1
                                        _SQL,
                                        'wiki-'+pageName
                                       );
            }.importSymbols(nameof repo),
            
            loadPageArtifact: proc(pageName){
                return fsl.loadManifest( this.getLatestRid(pageName) );
            }.importSymbols(nameof fsl)
        }
    });
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<














































































Deleted bindings/s2/require.d/io.s2.

1
2
3
4
5
/* a require.s2 module which "hides" s2.io via the module
   interface. It could optionally load 'dll!mod_io', but that's
   an internal impl detail.
*/
return s2.io;
<
<
<
<
<










Deleted bindings/s2/require.d/json.s2.

1
2
3
4
5
/* a require.s2 module which "hides" s2.json via the module
   interface. It could optionally load 'dll!mod_json', but that's
   an internal impl detail.
*/
return s2.json;
<
<
<
<
<










Deleted bindings/s2/require.d/json2.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
affirm s2.json /* we need this as our basis */;
affirm s2.json.stringify /* was added later, might not be in all (two?) trees yet (ha!) */;
/**
   This object basically acts as a (mostly) drop-in replacement for
   s2.json, and it uses s2.json to implement most of its
   functionality. It uses a custom, script-side stringify() which is
   orders of magnitude less efficient (on several levels) than
   s2.json.stringify() (which is implemented in C), but allows
   overriding of to-JSON behaviour on a per-container or per-prototype
   basis.
*/
return {
    /** See s2.json.parse(). */
    parse: s2.json.parse,
    /** See s2.json.parseFile(). */
    parseFile: s2.json.parseFile,
    /**
       Converts the value v into a JSON string (or throws while trying).

       indention may be either a falsy value (for no intenation), a
       string (which gets prepended N times for N levels of
       indentation), or an integer: a positive value indents that many
       spaces and a negative value indents that many tabs.

       v need not be a root-level value (Object or Array), but may be
       a string, number, or boolean.

       Returns a string on success, throws on error.

       Notes about special cases:

       - If v or a prototype of v contains a function property named
       toJSON() then v.toJSON() is used in place of v for
       to-JSON-string conversion. The function must return some
       JSON-able form of v. e.g. an implementation for a Hashtable
       might return an Object in the form {keys:[...], values:[...]}.

       - Object _keys_ which are _not_ of type (string, integer, double)
       are elided from the output. Keys of numeric types are converted
       to strings for JSON key purposes.

       - Objects elide any keys which have value counterpart of
       undefined. JSON does not know 'undefined'. We "could" translate
       it to null here, but we instead opt to elide it.

       - The undefined value: if passed to this function, the string
       'null' is returned. undefined is also translated to 'null' in
       the context of array empty entries.
    */
    stringify: proc stringify(v, indention = stringify.config.indention){
        affirm ++stringify.level > 0;
        const ex = catch{
            typeinfo(isderefable v) && typeinfo(iscallable v.toJSON) && (v = v.toJSON());
            const f = tmap # typename v;
            affirm f /* Argument must be a known JSON-able type or have a toJSON() method. */;
            if('string'===typename f){
                affirm --stringify.level>=0;
                return f;
            }else if(!f.buffered){
                /* "Simple" conversions which do not recurse */
                const rc = f(v);
                affirm --stringify.level>=0;
                return rc;
            }else if(stringify.level>stringify.config.maxOutputDepth){
                throw exception('CWAL_RC_RANGE',
                                "Output depth limit ("+stringify.config.maxOutputDepth+
                                ") exceeded while generating JSON.");
            }else{
                affirm f.buffered /* f.buffered is set, so... */;
                const jbuf = s2.Buffer.new() /* gets appended to by f() */;
                f(v) /* appends all output to jbuf */;
                affirm --stringify.level>=0;
                affirm !jbuf.isEmpty();
                return jbuf.takeString();
            }
        };
        affirm --stringify.level>=0;
        assert ex /* or we couldn't have gotten this far */;
        throw ex;
    }.withThis(proc(){
        /**
           Public configuration for stringify(). Change these
           options to modify the defaults.
        */
        this.config = {
            /* Default indention used by stringify(). */
            indention: undefined,
            /* Separator for entries in arrays and object lists. */
            commaSeparator: ', ',
            /* Separator for keys and value in objects. */
            keyValSeparator: ': ',
            /* Max object/array depth to allow before erroring
               out. Remember that cycles will generally be detected
               before this happens, so this doesn't necessarily
               indicate that any cycles were encountered.
            */
            maxOutputDepth: 15
        };
        this.level = 0;
        return this;
    }).importSymbols({
        // some crazy scoping and var accesses going on here...
        /**
           Indents the output, if appropriate, based on the current
           call level (or the level specified by the 2nd
           parameter). If addNL is true, a newline is appended before
           the indentation. This is a no-op if stringify() is called
           with a falsy indention parameter.
         */
        indent: proc callee(addNL=true, level = stringify.level){
            indention || return;
            callee.idbuf || (callee.idbuf = s2.Buffer.new(64));
            if(callee.prevLevel !== level){
                callee.prevLevel = level;
                if('integer'===typename indention){
                    const len = (indention<0) ? -indention : indention;
                    affirm len >= 0;
                    callee.idbuf.length( len * level )
                        .fill((indention<0) ? 0x09 : 0x20);
                }else if('string' === typename indention){
                    callee.idbuf.reset();
                    for(var i = 0; i < level; ++i){
                        callee.idbuf << indention;
                    }
                }
            }
            addNL && (jbuf << '\n');
            jbuf << callee.idbuf;
        },
        /**
           A hashtable mapping typenames to either strings (for static
           conversions) or a function taking a value parameter. Those
           functions normally return a string, but if the function has
           a 'buffered' property which is truthy then its return
           result is ignored and instead a Buffer value named jbuf is
           made available to them, and they are expected to append all
           output there.
         */
        tmap: scope {
            const proxy4Obj = proc(v){
                v.mayIterate() || throw exception('CWAL_RC_CYCLES_DETECTED',"Cycles detected.");
                jbuf << '{';
                proxyEachProp.first = true;
                //proxyEachProp('LEVEL', stringify.level);
                const ex = catch v.eachProperty(proxyEachProp);
                indention && indent(true,stringify.level-1);
                jbuf << '}';
                ex && throw ex;
            }.importSymbols({
                // Object.eachProperty() proxy.
                proxyEachProp: proc callee(k,v){
                    undefined === v && return;
                    callee.first
                        ? callee.first = false
                        : jbuf << stringify.config.commaSeparator;
                    indention && indent();
                    const tk = typename k;
                    if('string'===tk){
                        jbuf << k.toJSONString();
                    }else if('integer'===tk||'double'===tk){
                        jbuf << '"' << k << '"';
                    }else{
                        return;
                    }
                    jbuf << stringify.config.keyValSeparator
                        << stringify(v,indention);
                }
            });
            const proxy4Array = proc(v){
                v.mayIterate() || throw exception('CWAL_RC_CYCLES_DETECTED',"Cycles detected.");
                jbuf << '[';
                proxyEachIndex.first = true;
                v.eachIndex(proxyEachIndex);
                indention && indent(true,stringify.level-1);
                jbuf << ']';
            }.importSymbols({
                proxyEachIndex: proc callee(v){
                    callee.first
                        ? callee.first = false
                        : jbuf << stringify.config.commaSeparator;
                    indention && indent();
                    jbuf << stringify(v,indention);
                }
            });

            proxy4Obj.buffered =
                proxy4Array.buffered = true
            /* tells stringify() to set up a buffer to send the
               results to. */;
            const nativeImpl = s2.json.stringify;
            {#
                array: proxy4Array,
                bool: nativeImpl,
                double: nativeImpl,
                exception: proxy4Obj,
                integer: nativeImpl,
                null: 'null',
                object: proxy4Obj,
                string: nativeImpl,
                undefined: 'null'
            }; // scope result
        }
    })/*stringify()*/
};
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
























































































































































































































































































































































































































Deleted bindings/s2/require.d/json2.test.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
requireS2(['json2'],proc(JSON){
    //JSON.stringify.config.indention = -1;
    const str = proc(v,indent=-1){
        return JSON.stringify(v,indent);
    };
    var s;
    assert '1' === str(1);
    assert '1.23' === str(1.23);
    assert '"hi, \\"there\\""' === str('hi, "there"');
    assert 'null' === str(null);
    assert 'null' === str(undefined)
    /* undefined will be elided in some contexts (object properties),
       translated to null in others (e.g. arrays). */;
    assert 'false' === str(false);
    assert 'true' === str(true);
    s = str({x:1, y:{z:'hi "there"', a:[1,2,"yo"]}, u: undefined, n: null});
    assert s.indexOf('"u":') < 0;
    assert s.indexOf('"n": null')>0;

    s = JSON.parse(str(exception(-1,"not an error")));
    assert 'not an error' === s.message;
    assert -1 === s.code;

    s = JSON.parse(str({a:{b:{1:2,3:4}}}));
    assert 2 === s.a.b.1;
    assert 4 === s.a.b.3
    /**
       BUT ACHTUNG: the integer _keys_ got converted to strings in the
       round trip because JSON only supports strings as
       keys. Supporting round-trip fideltity for non-string keys
       requires a layer of indirection, as demonstrated next...
    */;

    // Customizing toJSON for a non-POD type...
    var h = s2.Hash.new();

    // A stringify()-compliant toJSON() impl for Hashtables.
    h.toJSON = proc(){
        return {
            keys: this.entryKeys(),
            values: this.entryValues()
        }
    };

    // Just for symmetry (not used by the JSON API)...
    h.fromJSON = proc(jsonObj){
        ('string' === typename jsonObj) && (jsonObj = JSON.parse(jsonObj));
        this.clearEntries();
        if(('array' === typename jsonObj.keys) &&
           ('array' === typename jsonObj.values)){
            const that = this;
            jsonObj.keys.eachIndex(proc(v,i){
                that.insert(v, jsonObj.values[i]);
            });
        }
        return this;
    }.importSymbols(nameof JSON);

    // Now try serializing a hash...
    h.insert(1, "one");
    h.insert(2, "two");
    s = JSON.parse(str(h));
    assert 2 === s.keys.length();
    assert 2 === s.values.length();
    assert s.keys.indexOf(2)>=0;
    assert s.values.indexOf('two')>=0;

    h.clearEntries();
    assert undefined === h # 2;

    h.fromJSON(s);
    assert 'two' === h # 2;
    
});
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




















































































































































Deleted bindings/s2/require.d/ob.s2.

1
2
3
4
5
/* a require.s2 module which "hides" s2.ob via the module
   interface. It could optionally load 'dll!mod_ob', but that's
   an internal impl detail.
*/
return s2.ob;
<
<
<
<
<










Deleted bindings/s2/require.d/ostream.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
   A require() module providing an "ostream" object. It overloads the
   << operator and sends all arguments to s2out.

   This module was written before the s2out keyword existed. That
   keyword does the same thing but does so more efficiently, making
   this module entirely superfluous. It is retained solely as a basic
   example of how to write a require.s2 module.
*/
affirm typeinfo(iscallable s2out);
return {
    'operator<<': proc(){s2out<<argv.0; return this}
};
<
<
<
<
<
<
<
<
<
<
<
<
<


























Deleted bindings/s2/require.d/plugins/demo.s2.

1
2
3
4
5
6
7
8
9
10
11
12
return {
    isVirtual: true,
    foo: true,
    cacheIt: false,
    counter: 0,
    load: proc(name){
        print(__FLC,"Demo plugin: argv=",argv);
        this.lastArgs = argv;
        ++this.counter;
        return this;
    }
};
<
<
<
<
<
<
<
<
<
<
<
<
























Deleted bindings/s2/require.d/plugins/dll.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
   Uses s2.loadModule() to load s2 loadable modules. Usage:

   dll!moduleName?options...

   The return value: if moduleProperty option is NOT provided
   and the module produces only a single property (as most
   do), that property's value is returned, otherwise the whole
   "namespace object" passed to the module init routine is
   returned. That possibly has potential backfire cases, but
   none affecting any current modules.

   Options:

   ACHTUNG: Be aware that require() caching will hold the
   first result from this plugin's load() call (and
   generically _not_ caching DLL results is dangergous), so
   any options provided to this function via a require() call
   here are only honored on the first call. On subsequent
   calls require() will use the cached result.

   entryPoint=string: if set, it is used as the second param
   to s2.loadModule().

   moduleProperty=string: if set, the result of the call is
   the given property from the DLL module. The majority of
   DLLs install only a single property, and this function will
   return only that property in such cases (as described
   above).
*/
const mod = {
    cacheIt: true /* _not_ caching DLL-loaded resources is a
                     Reall Bad Idea, as the DLL can change
                     between invocations, leaving us with
                     different binary signatures. Also,
                     functions injected via DLLs need access
                     to the C side, which disappears if a DLL
                     is closed. */,
    prefix: ('string' === typename (var dllTmp = s2.getenv('S2_MODULE_PATH')))
        ? dllTmp.split(dllTmp.indexOf(';') >= 0 ? ';' : ':')
    : ['.'],
    suffix: ('string' === typename (dllTmp = s2.getenv('S2_MODULE_EXTENSIONS')))
        ? dllTmp.split(dllTmp.indexOf(';') >= 0 ? ';' : ':')
    : ['.so','.dll'],
    load: function(name,opt){
        var rc;
        affirm name && 'string' === typename name;
        rc = (opt && opt.entryPoint)
            ? s2.loadModule(name, opt.entryPoint, {})
            : s2.loadModule(name, {});
        affirm rc;
        if(opt && opt.hasOwnProperty('moduleProperty')){
            rc = rc[opt.moduleProperty];
        }else if(var keys=rc.propertyKeys();
                 1===keys.length()){
            rc = rc[keys.0];
        }
        return rc;
    }
};

mod.prefix.push( requireS2.home + s2.io.dirSeparator + 'dll'  );

return mod;
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
































































































































Deleted bindings/s2/require.d/plugins/fsl/blob.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
   require() plugin which fetches a blob from the repo db. It
   interprets its argument as a symbolic blob name (anything
   libfossil's symbol-to-RID conversions support).
*/
return requireS2(
    ['fsl/context',
     'fsl/db/repo' // so that we are ensured that repo is opened by this point
    ],         
    proc(fsl){
        return {
            isVirtual: true,
            _F: fsl,
            load: function(sym){
                return this._F.loadBlob(sym)
            }
            /* If we wanted to be really pedantic, we would
               require(['fsl/db/repo']...) inside load().
               Overkill.
            */
        }
    });
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<












































Deleted bindings/s2/require.d/plugins/fsl/manifest.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
   require() plugin which fetches a Manifest from the repo db,
   in the form of an Object which mimics the fsl_deck structure
   (see Fossil.Context.loadManifest()).

   It interprets its argument as a symbolic blob name (anything
   libfossil's symbol-to-RID conversions support).
*/
return requireS2(
    ['fsl/context',
     'fsl/db/repo' // so that we are ensured that repo is opened by this point
    ],
    proc(fsl){
        return {
        isVirtual: true,
            _F: fsl,
            load: function(sym){
                return this._F.loadManifest(sym);
            }
        }
    });
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










































Deleted bindings/s2/require.d/plugins/fsl/wikiByName.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
   require() plugin which fetches a wiki page from the repo db,
   in the form of an Object which mimics the fsl_deck structure
   (see Fossil.Context.loadManifest()).

   It interprets its argument as a wiki page name and throws if not
   found.
*/
return requireS2(['fsl/context', 'fsl/db/repo'], proc(fsl, repo){
    return {
        isVirtual: true,
        _F: fsl,
        _sql: <<<_SQL
        SELECT x.rid FROM tag t, tagxref x
        WHERE x.tagid=t.tagid 
        AND t.tagname='wiki-%1$q'
        ORDER BY mtime DESC LIMIT 1 _SQL,
        load: function(name){
            return this._F.loadManifest(
                this._F.db.selectValue(this._sql.applyFormat(name))
                ||| throw "No wiki such wiki page: "+name
            );
        }
    };
});
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


















































Deleted bindings/s2/require.d/plugins/json-cached.s2.

1
2
3
4
/** A clone of the json plugin, except that cacheIt is set to true. */
const mod = requireS2.getPlugin('json').copyPropertiesTo({});
mod.cacheIt = true;
return mod;
<
<
<
<








Deleted bindings/s2/require.d/plugins/json.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/** A require.s2 plugin which loads file content as JSON,
    using s2.json.parseFile(). It does _not_ cache its
    results. Use the json-cached plugin for the same
    effect but with cached results.
*/
return {
    cacheIt: false /*
                     We _generally_ don't want these cached because
                     they are "probably" used only once in most cases.

                     Use the 'json-cached' plugin if you want cached
                     JSON files.
                   */,
    suffix: ['.json'],
    prefix: // Append '/json' to all paths in the default plugin's path
        ((const p = [], suffix = s2.io.dirSeparator+'json'),
         requireS2.getPlugin('default').prefix.eachIndex(proc(v){
             p[] = v+suffix;
         }), p),
    load: proc(file){return s2.json.parseFile(file)}
};
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










































Deleted bindings/s2/require.d/plugins/placeholder.s2.

1
2
3
4
5
6
/* a placeholder. you'll know if you need it. */
return {
    isVirtual: true,
    cacheIt: true,
    load: proc(){ return {} }
};
<
<
<
<
<
<












Deleted bindings/s2/require.d/plugins/tmpl-compiled.s2.

1
2
3
4
5
6
7
8
9
10
11
12
/**
   Works like the tmpl plugin, but passes the file's contents
   throughs s2.tmpl() before returning it.
*/
return {
    cacheIt: false,
    // prefix: uses the defaults
    suffix: ['.tmpl'],
    load: proc(fn){
        return s2.tmpl(s2.Buffer.readFile(fn))
    }
};
<
<
<
<
<
<
<
<
<
<
<
<
























Deleted bindings/s2/require.d/plugins/tmpl.s2.

1
2
3
4
5
6
7
8
9
10
/** Loads file content as a buffer, intended
    for use with .tmpl files (for s2.tmpl()). */
return {
    cacheIt: false,
    // prefix: uses the defaults
    suffix: ['.tmpl'],
    load: function(name){
        return s2.Buffer.readFile(name);
    }
};
<
<
<
<
<
<
<
<
<
<




















Deleted bindings/s2/require.d/pubsub.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/**
  A require.s2 module implementing a basic publish-subscriber
  manager.

  Example usage:

  http://fossil.wanderinghorse.net/repos/cwal/index.cgi/finfo?name=s2/require.d/pubsub.test.s2

  Returns a class, each instance of which manages its own list
  of publishers and subscribers.
*/
return {
    __typename: 'PubSub',
    prototype: undefined,
    
    /**
       Constructor for use with the 'new' keyword.
       
       var pubber = new thisObj();
       assert pubber inherits thisObj; // this will hold

       Each instance maintains its own, independent list of
       subscriptions.
    */
    __new: proc(){
        this.reset();
    },

    /**
       Subscribes a callback to events published for a given key.
       key may be of any value type. func must be-a Function.
       
       Returns a unique-per-subscription value (of an unspecified
       type) which can be passed to unsub() to opt out of a
       subscription. For a permanent subscription, simply ignore the
       result value.
    */
    sub:proc callee(key, func){
        affirm typeinfo(iscallable func) /* is Function-like */;
        const m = (this.$map[key] ||| (this.$map[key]={prototype:null})),
              i = enum{k:key}.k;
        m[i] = func;
        return i;
    },

    /**
       Expects id to be a value returned by this.sub() and
       unsubscribes a subscriber registered with that id.

       Returns this object.
    */
    unsub: proc(id){
        affirm typeinfo(isunique id);
        (const c = this.$map[id.value]) && unset c[id];
        return this;
    },

    /**
       Publishes an event to all subscribers (if any) of the key
       (event type) given as the first argument.
       Important notes:

       a) Subscribers are notified in an UNSPECIFIED and (very)
       POSSIBLY CHANGING order.

       b) Any arguments given after the event key are passed to each
       subscriber callback function, in the order they are passed in
       here. e.g. if this function is passed ('foo', 'bar', 1) then
       each subscriber will be called with ('bar', 1). Pedantic
       side-note: each callback gets its own copy of the arguments
       array, to avoid unintended side-effects if a callback modifies
       its argv.

       Returns this object (for lack of a better option).

       It propagates any exceptions thrown by a subscriber, and any
       pending subscribers won't get called.

       Pedantic side-node: each subscriber gets its own copy of the
       arguments array, so it's safe to change it or keep a reference
       to it in the subscribers without affecting others.
    */
    pub:proc(/*key, event args...*/){
        return this.pubWithThis(argv.shift(), undefined, @argv);
    },

    /**
       A special case of pub() useful in certain code constellations.

       For each listener of event type e, its callback is called using
       t as the callback's "this" and passing on all arguments after
       the second. If t is undefined then each callback is its own
       'this' (as is conventional for s2).

       Returns this object.

       See pub() for more details.
    */
    pubWithThis: proc(e,t/*...*/){
        const m = this.$map[e] ||| return this;
        affirm typeinfo(isobject m);
        argv.shift(2);
        foreach(m=>k,f) f.apply(t?:f, argv.slice());
        return this;
    },

    /**
       Removes all subscriptions for the given key or (if no arguments
       are passed) all subscriptions for all keys.
       
       Returns this object (for lack of a better option).
    */
    reset:proc(/*key*/){
        argv.# ? unset this.$map[argv.0] : this.$map = {prototype:null};
        return this;
    }
};
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










































































































































































































































Deleted bindings/s2/require.d/pubsub.test.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/* Short demo of the pubsub require.s2 module. */
requireS2(
    ['nocache!pubsub'],
    proc(P){
        const p = new P();
        assert 'object' === typename p.$map /* testing internals */;
        print(__FLC, 'pubsub:');
        foreach(p=>k,v) print('\t',k,v);
        var counter = 0;
        const id = p.sub('hi', proc(){
            ++counter;
            print(__FLC,'hi handler 1',argv);
        });
        const id2 = p.sub('bye', proc(){
            ++counter;
            print(__FLC,'bye handler',argv);
        });

        const id3 = p.sub('hi', proc(){
            ++counter;
            print(__FLC,'hi handler 2',argv);
        });

        print(__FLC, 'subscription IDs =',id, id2, id3);
        print("Publishing events...");
        p.pub('hi',0, __FLC);
        assert 2 === counter;

        p.pub('nope',1, __FLC);
        assert 2 === counter;

        p.pub('bye', 2, __FLC);
        assert 3 === counter;

        p.pub('hi',3, __FLC);
        assert 5 === counter;
        p.unsub(id);
        p.pub('hi',4, __FLC);
        assert 6 === counter;
        p.pub('bye', 5, __FLC);
        assert 7 === counter;
        print(__FLC, 'done');

        var p2 = new P();
        assert p2.$map;
        assert p2.$map !== p.map;
        assert p2 !== p;
        assert !(p2 inherits p);
        assert p2 inherits p.prototype;
        assert p2.sub === p.sub;

        print(__FLC, 'really done');
        return p;
    }
);
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<














































































































Deleted bindings/s2/require.d/require.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
/**
   Implements a basic script module loading API similar to
   require.js (http://requirejs.org).

   In short, this module acts as a loader for arbitrary other content,
   be it s2 script code, raw file content, or "virtual" (non-file)
   content plugged in by the user. Its usage is modelled very much of
   off requirejs, and it will look familiar to anyone who has used
   that library.

   See the API docs in this module for more details. For even more
   details (or other details, in any case), see this public Google
   Doc:

   https://docs.google.com/document/d/14gRP4f-WWgWNS64KM_BI7YjQqBLl4WT3jIrmNmFefYU/view

   Example usage:

   Assume the files r1.s2 and r2.s2 live somewhere in this module's
   search path and that both return an Object with a foo() method.
   This is how we might import and use them:

   @code
   const R = import('path/to/require.s2');

   R(['r1', 'r2'], proc(r1, r2){
     r1.counter = r2.counter = 1;
     print(__FLC,r1);
     print(__FLC,r2);
     r1.foo();
     r2.foo();
     ++r1.counter;
     ++r2.counter;
   });

   R(['r2', 'r1'], proc(r2, r1){
     assert 2 === r2.counter;
     assert 2 === r1.counter;
     // ^^^ because imported script results are cached
   });
   @endcode

*/
affirm s2.fs;
affirm s2.Buffer;
affirm typeinfo(isfunction s2.getenv);
affirm typeinfo(isfunction s2.fs.realpath);
affirm typeinfo(isfunction s2.Buffer.readFile);
const  /* saves some var lookup time */
s2 = s2,
realpath = s2.fs.realpath,
getenv = s2.getenv,
cliFlags = (s2.ARGV ? s2.ARGV.flags : 0) ||| {prototype:null}
;

const importFileBuffer = s2.Buffer.readFile;
const importFileText = proc(fn) using(importFileBuffer) {
    return importFileBuffer(fn).takeString();
};
/**
   If a string-type CLI flag OR environment variable (in that
   order) with the given name is found, this function returns an
   array of its contents, tokenized using
   s2.PathFinder.tokenizePath(). If no flag/environment variable
   is found, or a CLI flag with a non-string value is found,
   undefined is returned.

   Interpretation of the arguments is as follows:

   - If only 1 argument is provided, this routine looks for both a
   CLI flag and environment var (in that order) with that name.

   - If 2 arguments are provided, the 1st is checked as a CLI flag
   and the 2nd as an environment variable (in that order).
*/
const pathFromEnv = proc(f,e=f){
    const p = F[f] ||| E(e);
    return typeinfo(isstring p)
        ? P.tokenizePath(p)
        : undefined;
} using {
    E: getenv,
    P: s2.PathFinder,
    F: cliFlags
};

/**
   Internal utility to convert almost-URL-encoded plugin options
   to some value form. Treats non-existent values (===undefined)
   as a boolean true flag.
*/
const convertQValue = proc(v){
    undefined === v && return true;
    'true' === v && return true;
    'false' === v && return false;
    'null' === v && return null;
    return 0.parseNumber(v) ?: v;
};

/**
   Module-internal cache of import() responses.
*/
const modCache = new s2.Hash(0.nthPrime(15));

/**
   The main "require" module object.
*/
const mod = {
    __typename: 'require.s2',
    /**
       Used for doing file lookups. Its search path and extensions
       get continually swapped out by require() and friends to use
       whatever search path the current context requires.
    */
    pf: new s2.PathFinder(),
    pathFromEnv,
    /* Exposed for corner-case use by plugins. */
    modCache
};

/**
   This "default" plugin (A) acts as the default
   implementation for fetching require()'d files, (B) is
   where the interface for other plugins is documented.
*/
const PluginModel ={
    /**
       Clear the prototype - we don't really need it for
       this object.
    */
    prototype: undefined,
    /** If true, require() does not search for the filename it
        is given. (It is assumed to be some form of virtual
        unique identifier, and may be empty.)
    */
    isVirtual: false,
    
    /**
       cacheIt tells require whether or not to cache the results
       of requests to this plugin. If it is true, then this.load()
       will only be called for the first request, and its result
       will be returned on subsequent requests. EXCEPTION: if
       a plugin is called with arguments (see load()) the cache
       is ignored/bypassed.

       The default is expected (by client code) to always be true
       for the default plugin!
    */
    cacheIt: true,

    /**
       For file-based plugins, prefix specifies the search
       directories (array of strings, excluding any trailing slash
       on the directory parts unless they refer to the root
       directory).
    */
    prefix: pathFromEnv('s2.require.path', 'S2_REQUIRE_PATH') ||| ['.'],
    /**
       For file-based plugins, suffix specifies the search
       extensions (array of strings, including any '.' part,
       e.g. use ".foo" instead of "foo" unless you're searching
       for partial extensions).
    */
    suffix: pathFromEnv('s2.require.extensions', 'S2_REQUIRE_EXTENSIONS') ||| ['.s2'],

    /**
       Called by require() to "load" a given module.  load() must
       accept a module name, "load" that module, for a given
       definition of "load", and return its contents or
       result. (It need not actually load anything from anywhere,
       much less a file: it might simply return a predefined
       object or other value.)

       In the call context, 'this' will be this plugin
       configuration object.

       The first argument is the "name" part of the string passed
       to require.

       The second argument part is either undefined or an object:
       if the file string contains '?', anything after the ? is
       assumed to be encoded in the form a=b&c=d... (URL-like, but
       _without_ the URL encoding), and that gets transformed into
       an Object of key/value pairs before passing them to this
       function (the default value, if no "=" is provided, is boolean
       true). HOWEVER: passing any arguments after '?' will
       cause caching to be bypassed, because the arguments presumably
       change how the plugin works. Note that if nothing follows
       the '?' then no options object is created.
       
       Caveats regarding the opt parameter:

       1) all of the values in the opt object will originally be
       of type string, but numeric-looking strings and the strings
       ("true", "false", "null") get converted to their
       script-native type.

       2) Only one level of opt object is supported, not nested
       objects, arrays, etc.
    */
    load: proc(f/*, opt*/) using({
        b: s2.Buffer.readFile,
        r: mod
    }) {
        return b(f).evalContents(f,{requireS2: r});
    }
    /* Reminder to self: we cannot simply alias to s2.import() because
       any extra non-string arguments would be passed to it (doing the
       wrong thing). Reminder #2: adding comments outside of function
       bodies, instead of if them, uses less memory, since they don't
       get allocated as part of the function body. That's especially
       significant when the length of the comments outweigh the rest
       of the source, as in this case. */
};

/**
   The funkiness we do with 'this' vs 'mod' in may places places
   below is so that this stuff still works when clients copy the
   returned module into a property of another object. i.e. the following
   usages are equivalent:

   var x = mod;
   obj.x = mod;
   x([...],...);
   obj.x([...],...);

   Both calls (because of these extra bindings) use the same "this"
   inside the call (the local 'mod' symbol), which is important for
   identical/correct semantics in both uses.
*/

/**
   All the plugins are stored in this object, and any number may
   potentially be loaded (and added here) via client-side use.
*/
mod.plugins = {
    /**
       The name 'default' is magic and assumes certain
       plugin-level defaults. It also provides the default .prefix
       and .suffix properties for other non-isVirtual (file-using)
       plugins (used only if a given plugin does not define them
       itself).
    */
    default: PluginModel,
    /**
       Works just like the default plugin but bypasses
       the cache.
    */
    nocache: {
        cacheIt: false,
        load: PluginModel.load
    },
    /** Loads file content as a string. */
    text: {
        cacheIt: false,
        // prefix: uses the defaults
        suffix: ['.txt', '.s2', '.html'],
        load: importFileText
    },
    /** Loads file content as a buffer. */
    buffer: {
        cacheIt: false,
        // prefix: uses the defaults
        suffix: ['.txt', '.s2', '.html'],
        load: importFileBuffer
    }

    /**
       Demonstration of a "virtual" plugin (one which does not
       use files).
       
       virtualDemo:{
       isVirtual: true,
       cacheIt: true, // not strictly necessary, plugin-dependent
       load: proc f(fn,opt){
       print("Example of a 'virtual' handler. Arguments:",argv);
       return opt ||| this;
       }
       }
    */

};

/**
   Installs one or more plugins into this object.

   If called with an initial string arugment then:

   - Adds a new plugin. name must be a string and pluginObj must
   be an object which follows the PluginModel interface. Returns
   pluginObj on success.

   If called with a non-string argument then:

   - name is assumed to be a container. Each of its properties is
   assumed to be a module definition, and each one gets installed
   via a call back into this function, passing it each key and
   value. Returns this object.

   Throws on error.
*/
mod.addPlugin = proc callee(name, pluginObj){
    if(name && !pluginObj && typeinfo(iscontainer name)){
        foreach(name=>k,v) callee(k,v);
        return this;
    }
    affirm 'string' === typeinfo(name name);
    affirm name /* name must be non-empty */;
    affirm typeinfo(iscontainer pluginObj) && typeinfo(iscallable pluginObj.load);
    mod.plugins[name] = pluginObj;
    return mod;
} using(mod);

/**
   Searches for a plugin script by using this.plugins.default's
   search path and the name ("plugins/"+name). Returns undefined
   if not found, else the result of s2.import()'ing that file. It
   does not check if the plugin is already installed, but installs
   (or overwrites) it into mod.plugins[name].
*/
mod.searchAndInstallPlugin = proc(name){
    mod.pf.prefix = mod.plugins.default.prefix;
    mod.pf.suffix = mod.plugins.default.suffix;
    const fn = mod.pf.search('plugins/'+name);
    return fn
        ? mod.plugins[name] = ((const requireS2=mod), import(false,R(fn)))
        : undefined;
} using {mod, R: realpath},

/**
   If the given plugin name is already installed, it is returned,
   otherwise it is sought for, installed, and returned. An
   exception is thrown if it cannot be found or if installing it
   fails.
*/
mod.getPlugin = proc(name) using(mod) {
    return mod.plugins[name] ||| mod.searchAndInstallPlugin(name);
},

/**
   Attempts to resolve a file name using a given plugin's search
   path. basename is the unresolved name of the file to search for
   and forPlugin is either a plugin object or the name of a
   plugin. The search path/extensions use are those of the given
   plugin or (if that plugin has none), the default plugin. If the
   given plugin has the isVirtual flag, no search is performed and
   the undefined value is returned.
*/
mod.resolveFilename = proc(basename,forPlugin='default') using(mod){
    const pConf = typeinfo(isobject forPlugin) ? forPlugin : mod.getPlugin(forPlugin);
    affirm typeinfo(isobject pConf);
    const pf = (pConf.isVirtual ? undefined : mod.pf) ||| return;
    pf.prefix = pConf.prefix ||| mod.plugins.default.prefix;
    pf.suffix = pConf.suffix ||| mod.plugins.default.suffix;
    return pf.search(basename,0);
};

/**
   Given a base filename...

   1) If the given name does not contain a '!' character,
   it searches for an exact-match name in the cache. If it
   finds one, it returns that value.

   2) It searches for the file using the configured search
   paths/extensions (from this.plugins). If found, it is passed to
   the import() function specified for the import type (see
   below).

   By default this.plugins.default is used to search for and
   import the file. If a "special" type of name is provided to
   this function, though (meaning the base name looks like
   with "SOMETHING!basename"), then this.plugins[SOMETHING] is
   used (if set), which may change the caching behaviour and
   how the content of the file is interpreted.

   Depending on the configuration options, requests might get
   cached. Subsequent calls which expand to the same file name
   will return the same (cached) result value on each
   subsequent call.
*/
mod.import = proc(basename){
    affirm 'string' === typeinfo(name basename);
    if(basename.indexOf('!')<0 && const c = C#basename){
        return c;
    }
    var pluginName;
    /* Check for pluginName!... */
    if((var ndx = basename.indexOf('!'))>0){
        pluginName = basename.substr(0,ndx);
        basename = basename.substr(ndx+1);
    }
    pluginName || (pluginName ='default');
    /* Configuration for this plugin... */
    const pConf = mod.getPlugin(pluginName);
    pConf || throw "Could not load plugin '"+pluginName+"'.";
    const pf = pConf.isVirtual ? undefined : mod.pf /* PathFinder */;
    if(pf){
        // Set up/reset file lookup paths/suffixes...
        pf.prefix = pConf.prefix ||| mod.plugins.default.prefix;
        pf.suffix = pConf.suffix ||| mod.plugins.default.suffix;
    }
    /* Treat ?a=b&c=d... almost like a URL-encoded
       as query string, but without the URL encoding.
    */
    var qArgs, useCache = pConf.cacheIt;
    if(basename.indexOf('?')>=0){
        /* Parse args. If any are provided, bypass the cache. */
        const sp = basename.split('?',2);
        basename = sp.0;
        (qArgs = Q(sp.1)) && (useCache = false);
    }
    /* Find the file, if necessary... */
    var fn = pConf.isVirtual
        ? basename
        : (basename ? pf.search(basename, 0) : false)
        ||| throw "Plugin '%1$s' cannot find '%2$s' in search path %3$J with extensions %4$J.".
        applyFormat(pluginName, basename, pf.prefix, pf.suffix);
    // expand to the fully qualified path for non-virtual plugins...
    pConf.isVirtual || (fn = P(fn));
    //print(__FLC,"fn=",fn,"useCache=",useCache,"qArgs =",qArgs,pluginName,pConf);
    //print(__FLC,'pConf.cacheIt=',pConf.cacheIt,', fn=',fn);
    //print(__FLC,cache.toJSONString(2));
    const requireS2 = mod /* public API symbol, potentially
                             needed by anything which
                             uses anyPlugin.load() */;
    return useCache
        ? (const k =
                 (pluginName ? pluginName+'!'+fn : fn),
                 cacheCheck = (C # k))
        ? cacheCheck
        : C.insert(k, pConf.load(fn, qArgs))
    : pConf.load(fn, qArgs);
} using(mod, {
    C: modCache,
    P: realpath,
    Q: proc(str) using {q:convertQValue} {
        str || return str;
        var r;
        foreach(@str.split('&')=>v){
            const s = v.split('=',2);
            (r ||| (r = {prototype:null}))[s.0] = s.1 ? q(s.1) : (v.indexOf('=')>0 ? s.1 : true);
        };
        return r;
    }
});

/**
   For each base filename in the given array/tuple, this function
   calls this.import(basename) and appends the results to a new
   array.  Returns an array of the results to each import() call,
   the order of the array's elements being the same as the calls
   to import(). Throws if list is not an array or is empty or if
   loading any resources fails. Propagates any exceptions.
*/
mod.importList = function(list) using(mod) {
    affirm typeinfo(islist list) /* expecting an Array or Tuple */;
    affirm !list.isEmpty() /* expecting a non-empty list of module names */;
    const imps = [];
    foreach(@list=>v) imps.push(mod.import(v));
    return imps;
};

/**
   Imports a list of script modules and optionally calls a
   callback after loading all of them.

   list is an array of strings - script file base names to import.
   func is an optional function or code string which gets called
   resp. eval'd after importing all of the scripts. If it's a
   function, it is passed one argument for each entry in the list,
   in the same order they are imported. If it is a code string
   then it is eval'd in a scope with the array 'modules' defined
   to the resolved list of modules.

   In either case, it declares the const symbol requireS2 to be
   the require() module, so that these callbacks may recursively
   invoke require() via a call to requireS2(). (It is often useful
   to do so, it turns out, and this consolidates the convention
   across modules originally written for different code bases.)

   Returns the result of calling the function (if any) or the
   array of loaded modules (if passed no function/string).

   Example:

   assert 42 === thisObj(
   ['module1', 'module2'],
   function(mod1, mod2){
   print('result of module1 import:' mod1);
   print('result of module2 import:' mod2);
   return 42;
   }
   );


   _Potential_ TODOs:

   - If passed a non-string value where a name is expected, use it
   as-is as the result of the loading. i suspect we might have
   some interesting uses for that, but want a use case before trying
   it out.
*/
mod.require = function(list, func) using(mod) {
    func && affirm (typeinfo(iscallable func) || typeinfo(isstring func));
    list = mod.importList(list);
    func || return list;
    if(typeinfo(iscallable func)){
        const requireS2=mod, s2 = s2;
        return func.apply(func, list);
    }else{
        return func.evalContents('require() script',{
            s2: s2,
            requireS2: mod,
            modules: list
        });
    }
};

/**
   Installs a cached entry for the given module name, such that
   future calls to import() or require() which use that exact name
   will return the given result object. Note that the name need
   not be filesystem-friendly. e.g. "<my-identifier>" is perfectly
   legal. The only limitation is that it "really should not"
   contain an exclamation point, as that may confuse import()
   because that character is used to denote a plugin.
*/
mod.installModule = function(name, result) using(modCache){
    modCache.insert(name, result);
    return this;
};


// Try to determine some useful directories to search for scripts in...
if(typeinfo(isstring var d = cliFlags['s2.require.home'])){
    mod.home = realpath(d);
    mod.plugins.default.prefix.push( mod.home );
}
else if((d=(cliFlags['s2.home']|||getenv('S2_HOME')))
        && (d=realpath(d+'/require.d'))){
    mod.plugins.default.prefix.push( mod.home = d );
}
if((var d = __FILEDIR ? realpath(__FILEDIR) : 0)
   && (mod.home !== d)){
    mod.plugins.default.prefix.push( d );
    mod.home || (mod.home = d);
}
/* __FILEDIR may be a synthetic __FILE name
   (e.g. via eval or Buffer.evalContents()) */
mod.home || (mod.home="");

/* We set mod.require.home so that plugins can construct paths via
   requireS2.home. */

/* Make it so that call()ing this object calls mod.require() */
mod.prototype = mod.require /** Holy cow! We've just inherited our
                                own member function. */;
mod /* script result */;
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted bindings/s2/require.d/require.test.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

affirm requireS2;
affirm 'string' === typename requireS2.home;
affirm requireS2.home.indexOf('/')>-1 || requireS2.home.indexOf('\\')>-1;
//print(__FLC,'requireS2.home =',requireS2.home);
/**
   A quite incomplete test of require.s2. Since this cannot even
   be loaded by require.s2 if require.s2 isn't at least basically
   working, just getting here already tells us much.
*/
requireS2.addPlugin('dummy',{
    isVirtual: true,
    cacheIt: false,
    load: proc(n,opt){
        return {
            name: n,
            opt: opt
        };
    }
});

requireS2.addPlugin({
    dummy2:{
        isVirtual: true,
        chachIt: true,
        load: proc(n,opt){
            return 1;
        }
    },
    dummy3:{
        isVirtual: true,
        chachIt: true,
        load: proc(n,opt){
            return 0;
        }
    }
});

requireS2(['dummy!fred?a=1'],proc(obj){
    affirm 'fred' === obj.name;
    affirm 1 === obj.opt.a;

    assert true === requireS2(['dummy!barny', 'dummy2!', 'dummy3!'],proc(d, d2, d3){
        assert 'barny' === d.name;
        assert !d.opt;
        assert 1 === d2;
        assert 0 === d3;
        return true;
    });
});
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




































































































Deleted bindings/s2/require.d/time.s2.

1
2
3
4
5
/* a require.s2 module which "hides" s2.time via the module
   interface. It could optionally load 'dll!mod_time', but that's
   an internal impl detail.
*/
return s2.time;
<
<
<
<
<










Deleted bindings/s2/require.d/tmpl.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
   The tmpl module (as distinct from the tmpl plugin!) provides
   utilities for working with s2.tmpl().

*/
return {
    /**
       "Processes" a s2.tmpl() template as follows...

       The first argument is a tmpl()-compiled script (of type Buffer)
       or a non-compiled script of type String (in which case this
       function compiles it and uses the original value as the 3rd
       parameter).

       The second parameter is an optional container holding key/value
       pairs which get imported into the current scope before
       evaluating the script. This allows one to easily create
       mini-templates for use in loops and such. If you _have_
       to pass a value but don't have an object, any falsy value
       will do.

       The final argument is intended to hold the uncompiled script
       and is only used in error reporting, and is stored in any
       exception propagated via evaluating a template. It is ignored
       when the first argument has a typename of 'string'.
    */
    process: proc(template, opt, tmplUncompiled){
        if(typeinfo(isstring template)){
            tmplUncompiled = template;
            template = this.compile(template);
        }
        affirm typeinfo(isbuffer template);
        if(const ex = catch template.evalContents(opt|||{})){
            ex && throw {
            message: "Error evaluating compiled template (location info is relative to the compiled script).",
            exception: ex,
                template: {
                    compiled: template.toString(),
                    uncompiled: tmplUncompiled ? tmplUncompiled.toString() : undefined
                }
            };
        }
    },

    processFile: proc(fn, opt){
        return this.process( this.load(fn, true), opt );
    },

    /**
       A proxy for s2.tmpl().
    */
    compile: s2.tmpl,
    /**
       Uses the tmpl! plugin to load the given file
       and optionally compile it using this.compile()
    */
    load: proc(fn, compile){
        return requireS2([(compile ? 'tmpl-compiled!' : 'tmpl!')+fn]).0;
    } using (requireS2)
};
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
























































































































Deleted bindings/s2/require.d/tmpl.test.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* A brief test of the 'tmpl' require.s2 module. */
requireS2(
['ob',
 'nocache!tmpl'
 // bug:  ^^^^ reading a directory (by accident) is reported as OOM.
 // Fixed by not allowing PathFinder to resolve directory names, but
 // that support is missing for Windows (patches welcomed).
],
proc(ob, t){
    const src = 'a=<% a %>, b=<%b%>, c=<%c%>\n';
    const obLevel = ob.level();
    var str;
    ob.push();
    var ex = catch{
        t.process(src,{
            a:'hi',
            b:'there',
            c:'world'
        });
        str = ob.pop(-1);
    };
    while(ob.level()>obLevel) ob.pop();
    ex && throw ex;
    affirm "a=hi, b=there, c=world\n" === str;

    scope {
        const src2 = '<%a%>,<%b%>,<%c%>\n',
          compiled = t.compile(src2),
          opt = { a:-1, b:0, c:1 }
          ,TMPLOUT = proc(){} /* eval'd template uses this func, if defined */
        ;
        const XYZ = 'hi';
        t.process('<% XYZ %>\n', null);
        for(var i = 0; i < 5; ++i, ++opt.a, ++opt.b, ++opt.c ){
            t.process(compiled, opt, src);
        }
        affirm 4==opt.a;
        affirm 5==opt.b;
        affirm 6==opt.c;
    };
    print(__FLC,'done!');
});
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




















































































Deleted bindings/s2/s2_amalgamation.c.

more than 10,000 changes

Deleted bindings/s2/s2_amalgamation.h.

more than 10,000 changes

Deleted bindings/s2/shell2.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/**
   s2sh2 - a cleanup of s2sh.

   s2sh2 is functionally *nearly* identical to s2sh, the primary
   difference being that s2sh2 has more conventional/cleaned-up CLI
   argument handling. Many of the flags which are toggles in s2sh have
   only a single form in s2sh2, to turn something on or off, but not
   toggle it.

   Other minor differences:

   - $2 is a UKWD referring to the global s2 object.
*/

#define S2SH_VERSION 2
#include "shell_common.c"

/**
   gcc bug: if this function is marked static here, it does
   not recognize that shell_common.c uses it and declares that
   it's unused.
*/
static CliAppSwitch const * s2sh_cliapp_switches(){
  static const CliAppSwitch cliAppSwitches[] = {
  /* {opaque, dash, key, value, brief, details, callback, reserved} */
  /** Fills out most of a CliAppSwitch entry */
#define F6(IDFLAGS,DASHES,KEY,VAL,BRIEF,PFLAGS)     \
  {IDFLAGS, DASHES, KEY, VAL, BRIEF, 0, 0, PFLAGS}
  /** Continues the help text for the previous entry. */
#define FCONTINUE(LABEL) {SW_CONTINUE, 0, 0, 0, LABEL, 0, 0, 0}
  /** Adds an "additional info" line, not indented. */
#define FINFOLINE(LABEL) {SW_INFOLINE, 0, 0, 0, LABEL, 0, 0, 0}
  /** -short | --long[=VALUE]*/
#define FX(IDFLAGS,SHORT,LONG,VALUE,BRIEF,PFLAGS)   \
  F6(IDFLAGS,2,LONG,VALUE,BRIEF,PFLAGS),            \
  F6(IDFLAGS,1,SHORT,VALUE,0,PFLAGS)
  /** -short, --long on/off switches ...*/
#define F01(IDFLAGS,SHORT,LONG,BRIEF)                       \
  {IDFLAGS+1, 2, LONG,   0, "Enable "  BRIEF, 0, 0, 0},     \
  {IDFLAGS+1, 1, SHORT,  0, "Enable "  BRIEF, 0, 0, 0},     \
  {IDFLAGS,   2, "no-"LONG, 0, "Disable " BRIEF, 0, 0, 0},  \
  {IDFLAGS,   1, "no"SHORT, 0, "Disable " BRIEF, 0, 0, 0}
  /** Start the next group (for --help grouping). */
#define GROUP(GID,DESC) {GID, 0, 0, 0, DESC, 0, 0, 0}

  /***********************************************************************/
  GROUP(SW_GROUP_0,0),

  /**
     Keep these arranged so that flags with short and long forms are
     ordered consecutively, long one first. The help system uses that to
     collapse the flags into a single listing. In such cases, the help
     text from the first entry is used and the second is ignored.
  */
  FX(SW_HELP, "?", "help", 0,
     "Send help text to stdout and exit with code 0.", 0),
  FCONTINUE("The -v flag can be used once or twice to get more info."),

  FX(SW_VERBOSE,"v","verbose",0,
     "Increases verbosity level by 1.", 0),

  FX(SW_VERSION,"V","version",0,
     "Show version info and exit with code 0. ", 0),
  FCONTINUE("Use with -v for more details."),

  FX(SW_ESCRIPT,"e","eval","SCRIPT",
     "Evaluates the given script code in the global scope. "
     "May be used multiple times.", CLIAPP_F_SPACE_VALUE),

  FX(SW_INFILE,
     "f","file","infile",
     "Evaluates the given file. The first non-flag argument is used "
     "by default.", CLIAPP_F_SPACE_VALUE | CLIAPP_F_ONCE),

  FX(SW_OUTFILE, "o", "output", "outfile",
     "Sets cwal's output channel to the given file. Default is stdout.",
     CLIAPP_F_SPACE_VALUE | CLIAPP_F_ONCE),

  /***********************************************************************/
  GROUP(SW_GROUP_1, "Less-common options:"),

  FX(SW_ASSERT_1,"A", "assert", 0, "Increase assertion trace level by one: "
     "1=trace assert, 2=also trace affirm.",0),

  FX(SW_INTERACTIVE,"i", "interactive", 0,
     "Force-enter interactive mode after running -e/-f scripts.",0),

  FX(SW_AUTOLOAD_0,"I","no-init-script", 0,
     "Disables auto-loading of the init script.",0),
  FCONTINUE("Init script name = same as this binary plus \".s2\" "),
  FCONTINUE("or set via the S2SH_INIT_SCRIPT environment variable."),
  
  /***********************************************************************/
  GROUP(SW_GROUP_2, "Esoteric options:"),

  FX(SW_METRICS, "m", "metrics", 0,
     "Show various cwal/s2 metrics before shutdown.",0),
  FCONTINUE("Use -v once or twice for more info."),

  F01(SW_MOD_INIT_0, "mi", "module-init",
      "automatic initialization of static modules."),

  F6(SW_SHELL_API_1,2,"s2.shell",0,
     "Forces installation of the s2.shell API unless trumped by --cleanroom.",0),
  FCONTINUE("By default s2.shell is only installed in interactive mode."),

  F6(SW_TRACE_CWAL,2,"trace-cwal", 0,
     "Enables tremendous amounts of cwal_engine "
     "tracing if it's compiled in.",0),
  
  F01(SW_SCOPE_H0, "hash", "scope-hashes",
      "hashes for scope property storage (else objects)."),

  FX(SW_HIST_FILE_1,
     "h", "history", "filename",
     "Sets the interactive-mode edit history filename.",
     CLIAPP_F_SPACE_VALUE | CLIAPP_F_ONCE),

  F6(SW_HIST_FILE_0,2, "no-history", 0,
     "Disables interactive-mode edit history.", 0),
  F6(SW_HIST_FILE_0,1, "noh", 0, 0, 0),

  F6(SW_DISABLE, 2, "s2-disable", "comma,list",
     "Disables certain s2 API features by name.",0),
  FCONTINUE("Use a comma-and/or-space-separated list with any of "
            "the following entries:"),
  FCONTINUE("fs-stat, fs-read, fs-write, fs-io, fs-all"),
  
  F6(SW_CLEANROOM,2,"cleanroom",0,
     "Only core language functionality with no prototype-level methods.",0),
  FCONTINUE("Disables the s2/$2 global object and auto-loading of the "
            "init script."),

  FINFOLINE("Recycling-related options:"),
  F01(SW_RE_V0,"rv", "recycle-values", "recycling of value instances."),
  F01(SW_RE_C0,"rc","recycle-chunks", "the memory chunk recycler."),
  F01(SW_RE_S0,"si","string-interning", "string interning."),

  FINFOLINE("Memory-tracking/capping options:"),
  FX(SW_MCAP_TOTAL_ALLOCS, "cap-ta", "memcap-total-allocs", "N",
     "* Limit the (T)otal number of (A)llocations to N.",
     CLIAPP_F_SPACE_VALUE),
  FX(SW_MCAP_TOTAL_ALLOCS, "cap-tb", "memcap-total-bytes", "N",
     "* Limit the (T)otal number of allocated (B)ytes to N.",
     CLIAPP_F_SPACE_VALUE),
  FX(SW_MCAP_TOTAL_ALLOCS, "cap-ca", "memcap-concurrent-allocs", "N",
     "Limit the (C)oncurrent number of (A)llocations to N.",
     CLIAPP_F_SPACE_VALUE),
  FX(SW_MCAP_TOTAL_ALLOCS, "cap-cb", "memcap-concurrent-bytes", "N",
     "Limit the (C)oncurrent number of allocated (B)ytes to N.",
     CLIAPP_F_SPACE_VALUE),
  FX(SW_MCAP_TOTAL_ALLOCS, "cap-sb", "memcap-single-alloc", "N",
     "Limit the size of any (S)ingle allocation to N (B)ytes.",
     CLIAPP_F_SPACE_VALUE),
  FINFOLINE("The allocator starts failing (returning NULL) when a capping constraint is violated."),
  FCONTINUE("The 'tb' and 'ta' constraint violations are permanent (recovery requires resetting the engine)."),
  FCONTINUE("The 'ca' and 'cb' constraints are recoverable once some cwal-managed memory gets freed."),
  FCONTINUE("Capping only applies to memory allocated by/via the scripting\n"
            "engine, not 3rd-party APIs."),

  F6(SW_MEM_FAST1,2,"fast",0,"(F)orce (A)lloc (S)ize (T)racking.",0),
  FCONTINUE("Enables more precise tracking and reuse of recycled memory."),
  FCONTINUE("Several memcap options enable --fast implicitly."),
  
#undef GROUP
#undef F01
#undef F6
#undef FX
#undef FPLUS
#undef FPLUSX
#undef FINFOLINE
    CliAppSwitch_sentinel /* MUST be the last entry in the list */
  };
  return cliAppSwitches;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


































































































































































































































































































































































Deleted bindings/s2/shell_common.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */

/************************************************************************

This file is intended to be included directly into shell.c
(a.k.a. s2sh) and shell2.c (a.k.a. s2sh2), not compiled by itself. It
contains all of the code which is common to both versions of the shell
(which is everything but their --help bits).

************************************************************************/

#ifndef S2SH_VERSION
#  error define S2SH_VERSION to 1 or 2 before including this file.
#elif (S2SH_VERSION!=1) && (S2SH_VERSION!=2)
#  error S2SH_VERSION must bet to 1 or 2.
#endif

#include <assert.h>

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#if defined(S2_AMALGAMATION_BUILD) || 2==S2SH_VERSION
#  include "s2_amalgamation.h"
#else
#  include "s2.h"
#endif
/* ^^^^ may include config stuff used by system headers */

#if !defined(S2SH_FOR_UNIT_TESTS)
/* This option enables options which are only in place for
   purposes of s2's own unit test suite. e.g. to communicate
   compile-time options which may change test paths.
*/
#  define S2SH_FOR_UNIT_TESTS 0
#endif

#include "cliapp.h"

#if defined(S2_OS_UNIX)
#  include <unistd.h> /* isatty() and friends */
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <locale.h> /* setlocale() */

/* Must be defined by shell.c/shell2.c in a way compatible
   with cliApp.switches. */
static CliAppSwitch const * s2sh_cliapp_switches();

/**
   ID values and flags for CLI switches. Some are only used by one of
   s2sh or s2sh2.
*/
enum s2sh_switch_ids {
/** Mask of the bits used as flag IDs. The remainder are flag/modifier
    bits. */
SW_ID_MASK = 0xFFFF,
SW_ID_MASK_bits = 16,
#define ID(X) (X<< SW_ID_MASK_bits)

/**
   An entry flagged as SW_INFOLINE is intended to be a standalone line
   of information unrelated to a specific flag. If it needs to span
   lines, it must embed \n characters. --help outputs its "brief"
   member.
*/
SW_INFOLINE = ID(1),
/**
   An entry flagged with SW_CONTINUE must immediately follow either an
   SW_INFOLINE, a switch entry (SW_ID_MASK), or another entry with
   this flag. --help outputs its "brief" member, indented or not,
   depending on the previous entry.
*/
SW_CONTINUE = ID(2),

/**
   SW_GROUP_x are used for grouping related elements into tiers.
*/
SW_GROUP_0 = ID(4),
SW_GROUP_1 = ID(8),
SW_GROUP_2 = ID(0x10),
SW_GROUPS = SW_GROUP_0 | SW_GROUP_1 | SW_GROUP_2,

/**
   Switches with this bit set are not output by --help.
   (Workaround for s2sh -help/--help/-? having 3 options!)
*/
SW_UNDOCUMENTED = ID(0x20),

#undef ID

SW_switch_start = 0,
/*
  Entries for individual CLI switches...

  DO NOT bitmask the SW_GROUP entries directly with the
  switch-specific entries. They are handled in a separate step. Long
  story.

  The SW_ID_MASK bits of the switch ID values need not be bitmasks.
  i.e. they may have bits which overlap with other entries, provided that
  the SW_ID_MASK part is unique.

  All switches which *require* a value, as opposed to optionally
  accept a value, should, for consistency, have their pflags member
  OR'd with CLIAPP_F_SPACE_VALUE.

  All switches which may only be provided once must have their pflags
  field OR'd with CLIAPP_F_ONCE.
*/
SW_CLAMPDOWN/*v1. No longer allowed.*/,
SW_CLEANROOM,
SW_DISABLE,
SW_ESCRIPT,
SW_HELP,
SW_INFILE,
SW_INTERACTIVE,
SW_INTERNAL_FU,
SW_METRICS,
SW_OUTFILE,
SW_SHOW_SIZEOFS,
SW_SWEEP_INTERVAL,
SW_VAC_INTERVAL,
SW_VERBOSE,
SW_VERSION,

/**
  The entries ending with 0 or 1 are toggles. The #1 part of each pair
  MUST have a value equal to the corresponding #0 entry plus 1.
*/
SW_MEM_FAST0, SW_MEM_FAST1,
/** SW_RE_xxx = recycling-related options */
/* [no-]recycle-chunks */
SW_RE_C0, SW_RE_C1,
/* [no-]string-interning */
SW_RE_S0, SW_RE_S1,
/* [no-]recycle-values */
SW_RE_V0, SW_RE_V1,
/* [no-]scope-hashes */
SW_SCOPE_H0, SW_SCOPE_H1,
/* --T/-T */
SW_TRACE_STACKS_0, SW_TRACE_STACKS_1,
/* v1: --W/-w, v2 */
SW_TRACE_SWEEP_0, SW_TRACE_SWEEP_1,

/* v1: --s2.shell/-s2.shell, v2: na/--s2.shell */
SW_SHELL_API_0, SW_SHELL_API_1,
/* v1: --M/-M, v2: --no-module-init/na */
SW_MOD_INIT_0, SW_MOD_INIT_1,
/** v1: --A/-A */
SW_ASSERT_0, SW_ASSERT_1,
/** v1: --h/-h */
SW_HIST_FILE_0, SW_HIST_FILE_1,
/** v1: --a/-a, v2: na/-I
*/
SW_AUTOLOAD_0, SW_AUTOLOAD_1,

SW_TRACE_CWAL,

/* Memory-capping options... */
SW_MCAP_TOTAL_ALLOCS,
SW_MCAP_TOTAL_BYTES,
SW_MCAP_CONC_ALLOCS,
SW_MCAP_CONC_BYTES,
SW_MCAP_SINGLE_BYTES,

SW_end /* MUST be the last entry */
};


#if !defined(S2_SHELL_EXTEND_FUNC_NAME)
#  define S2_SHELL_EXTEND_FUNC_NAME s2_shell_extend
#endif
/*
  If S2_SHELL_EXTEND is defined, the client must define
  s2_shell_extend() (see shell_extend.c for one implementation) and
  link it in with this app. It will be called relatively early in the
  initialization process so that any auto-loaded scripts can make use
  of it. It must, on error, return one of the non-0 CWAL_RC_xxx error
  codes (NOT a code from outside the range! EVER!). An error is
  treated as fatal to the shell.

  See shell_extend.c for a documented example of how to use this
  approach to extending this app.
*/
#if defined(S2_SHELL_EXTEND)
extern int S2_SHELL_EXTEND_FUNC_NAME(s2_engine * se, cwal_value * mainNamespace,
                                     int argc, char const * const * argv);
#endif

/*
  MARKER((...)) is an Internally-used output routine which
  includes file/line/column info.
*/
#define MARKER(pfexp) \
  if(1) cliapp_print("%s:%d:  ",__FILE__,__LINE__); \
  if(1) cliapp_print pfexp
#define MESSAGE(pfexp) cliapp_print pfexp
#define WARN(pfexp) \
    if(1) cliapp_warn("%s:  ",App.appName); \
    if(1) cliapp_warn pfexp
#define VERBOSE(LEVEL,pfexp) if(App.verbosity >= LEVEL) { cliapp_print("verbose: "); MESSAGE(pfexp); }(void)0

/**
   S2_AUTOINIT_STATIC_MODULES tells the shell whether to initialize, or
   not, statically-linked-in modules by default.
*/
#if !defined(S2_AUTOINIT_STATIC_MODULES)
# define S2_AUTOINIT_STATIC_MODULES 1
#endif

/**
   S2SH_MAX_SCRIPTS = max number of scripts to run, including the -e
   scripts and the main script filename, but not the autoload script.
*/
#define S2SH_MAX_SCRIPTS 10

/**
   Global app-level state.
*/
static struct {
  char const * appName;
  char enableArg0Autoload;
  int enableValueRecycling;
  /**
     Max chunk size for mem chunk recycler. Set to 0 to disable.  Must
     allocate sizeof(void*) times this number, so don't set it too
     high.
  */
  cwal_size_t maxChunkCount;
  int enableStringInterning;
  int showMetrics;
  int verbosity;
  int traceAssertions;
  int traceStacks;
  int traceSweeps;
  char scopesUseHashes;
  char const * inFile;
  char const * outFile;
  char const * const * scriptArgv;
  int scriptArgc;
  int addSweepInterval;
  int addVacuumInterval;
  /**
     Name of interactive most history file. Currently
     looks in/writes to only the current dir.
  */
  char const * editHistoryFile;
  /**
     Default prompt string for the line-reading functions.
  */
  char const * lnPrompt;
  /**
     0=non-interactive mode, >0=interactive mode, <0=as-yet-unknown.
  */
  int interactive;
  /**
     This is the "s2" global object. It's not generally kosher to hold
     global cwal_values, but (A) we're careful in how we do so and (B)
     it simplifies a couple of our implementations.
  */
  cwal_value * s2Global;
  /**
     When shellExitFlag!=0, interactive readline looping stops.
  */
  int shellExitFlag;

  /**
     If true, do not install prototypes or global functionality.  Only
     "raw s2" is left.
   */
  int cleanroom;

  /**
     Flags for use with s2_disable_set_cstr().
  */
  char const * zDisableFlags;

  /**
     If true AND if "static modules" have been built in, this
     initializes those modules.
  */
  int initStaticModules;

  /**
     A flag used to tell us not to save the history - we avoid saving
     when the session has no commands, so that we don't get empty
     history files laying around. Once s2sh_history_add() is called,
     this flag gets set to non-0.

  */
  int saveHistory;

  /**
     A flag to force installation of the s2.shell API, even in non-interactive
     mode. <0 = determine automatically. 0 = force off. >0 = force on unless
     -cleanroom trumps it.
  */
  int installShellApi;

  /**
     Don't look at this unless you know _exactly_ what you're doing
     with s2's internals. It's for my eyes only.
   */
  int enableInternalFu;

  /**
     Memory-capping configuration. Search this file for App.memcap for
     how to set it up.
  */
  cwal_memcap_config memcap;

  /**
     Scripts for the -e/-f flags.
  */
  struct {
    int nScripts /*current # of entries in this->e*/;
    struct {
      /* True if this->src is a filename, else it's script code. */
      int isFile;
      /* Filename or script code. */
      char const * src;
    } e[S2SH_MAX_SCRIPTS];
  } scripts;
  int32_t cwalTraceFlags;
} App = {
0/*appName*/,
1/*enableArg0Autoload*/,
1/*enableValueRecycling*/,
50/*maxChunkCount*/,
1/*enableStringInterning*/,
0/*showMetrics*/,
0/*verbosity*/,
0/*traceAssertions*/,
0/*traceStacks*/,
0/*traceSweeps*/,
0/*scopesUseHashes*/,
0/*inFile*/,
0/*outFile*/,
0/*scriptArgv*/,
0/*scriptArgc*/,
0/*addSweepInterval*/,
0/*addVacuumInterval*/,
"s2sh.history"/*editHistoryFile*/,
#if 1==S2SH_VERSION
"s2sh> "/*lnPrompt*/,
#else
"s2sh2> "/*lnPrompt*/,
#endif
-1/*interactive*/,
0/*s2Global*/,
0/*shellExitFlag*/,
0/*cleanroom*/,
0/*zDisableFlags*/,
S2_AUTOINIT_STATIC_MODULES/*initStaticModules*/,
0/*saveHistory*/,
-1/*installShellApi*/,
0/*enableInternalFu*/,
cwal_memcap_config_empty_m/*memcap*/,
{0/*nScripts*/, {/*e*/{0,0}}},
0/*cwalTraceFlags*/
};


static cwal_engine * printfEngine = 0;
static void s2sh_print(char const * fmt, ...) {
  va_list args;
  va_start(args,fmt);
  if(printfEngine){
    cwal_outputfv(printfEngine, fmt, args);
  }else{
      cliapp_print(fmt,args);
  }
  va_end(args);
}

/**
   Called by cwal_engine_init(). This does the lower-level (pre-s2)
   parts of the cwal_engine configuration. This part must not create
   any values, as s2 will destroy them right after this is called so
   that it can take over ownership of the top-most scope. Thus only
   configurable options are set here, and new functionality is added
   during the s2 part of the initialization.
*/
static int s2sh_init_engine(cwal_engine *e, cwal_engine_vtab * vtab){
  int rc = 0;
  int32_t featureFlags = 1
    ? 0
    : CWAL_FEATURE_ZERO_STRINGS_AT_CLEANUP;

  cwal_engine_trace_flags(e, App.cwalTraceFlags);
  if(App.enableStringInterning){
    featureFlags |= CWAL_FEATURE_INTERN_STRINGS;
  }
  if(App.scopesUseHashes){
    /* If enabled, scopes will use hashtables for property storage.
       This are potentially more costly, in terms of memory, but
       are much faster. */
    featureFlags |= CWAL_FEATURE_SCOPE_STORAGE_HASH;
    if(vtab){/*avoid unused param warning*/}
  }else{
    featureFlags &= ~CWAL_FEATURE_SCOPE_STORAGE_HASH;
  }
  cwal_engine_feature_flags(e, featureFlags);

  {
    /* Configure the memory chunk recycler... */
    cwal_memchunk_config conf = cwal_memchunk_config_empty;
    conf.maxChunkCount = App.maxChunkCount;
    conf.maxChunkSize = 1024 * 32;
#if 16 == CWAL_SIZE_T_BITS
    conf.maxTotalSize = 1024 * 63;
#else
    conf.maxTotalSize = 1024 * 256;
#endif
    conf.useForValues = 0 /* micro-optimization: true tends towards
                             sub-1% reduction in mallocs but
                             potentially has aggregate slower value
                             allocation for most cases */ ;
    rc = cwal_engine_memchunk_config(e, &conf);
    assert(!rc);
  }

#define REMAX(T,N) cwal_engine_recycle_max( e, CWAL_TYPE_ ## T, (N) )
  REMAX(UNDEF,0) /* disables recycling for all types */;
  if(App.enableValueRecycling){
    /* A close guess based on the post-20141129 model...  List them in
       "priority order," highest priority last.  Lower prio ones might
       get trumped by a higher prio one: they get grouped based on the
       platform's sizeof() of their concrete underlying bytes.
    */
    REMAX(UNIQUE,20)/* will end up being trumped by integer (32-bit)
                       and/or double (64-bit) */;
    REMAX(KVP,80) /* guaranteed individual recycler */;
    REMAX(WEAK_REF,30) /* guaranteed individual recycler */;
    REMAX(STRING,50) /* guaranteed individual recycler */;
    REMAX(EXCEPTION,3);
    REMAX(HASH,15) /* might also include: function, native, buffer */;
    REMAX(BUFFER,20) /* might also include: function, native, buffer, hash */ ;
    REMAX(XSTRING,20 /* also Z-strings and tuples, might also include doubles */);
    REMAX(NATIVE,20)  /* might also include: function, hash */;
    REMAX(DOUBLE,50)/* might also include z-/x-strings,
                       integer (64-bit), unique (64-bit)*/;
    REMAX(FUNCTION,50) /* might include: hash, native, buffer */;
    REMAX(ARRAY,30);
    REMAX(OBJECT,30) /* might include: buffer */;
    REMAX(INTEGER,80) /* might include: double, unique */;
    REMAX(TUPLE,30) /* also x-/z-strings, might include: double */;
  }
#undef REMAX
  /*
    Reminder to self: we cannot install functions from here because
    the s2_engine's Function prototype will not get installed by that
    time, so the functions we added would not have an 'undefined'
    prototype property. We must wait until the s2_engine is set up.

    s2 will also nuke the top scope so that it can push one of its
    own, so don't allocate any values here.
  */
  return rc;
}

/** String-is-internable predicate for cwal_engine. */
static bool cstr_is_internable( void * state,
                                char const * str_,
                                cwal_size_t len ){
  enum { MaxLen = 32U };
  unsigned char const * str = (unsigned char const *)str_;
  assert(str);
  assert(len>0);
  if(len>MaxLen){
    if(state){/*avoid unused param warning*/}
    return 0;
  }else if(1==len){
    /*if(*str<32) return 0;
      else
    */
    return (*str<=127) ? 1 : 0;
  }
  /* else if(len<4) return 1; */
  else{
    /* Read a UTF8 identifier... */
    char const * zEnd = str_+len;
    char const * tail = str_;
    s2_read_identifier(str_, zEnd, &tail);
    if((tail>str_)
       && (len==(cwal_size_t)(tail-str_))
       ){
      /* MARKER(("internable: %.*s\n", (int)len, str_)); */
      return 1;
    }
    /* MARKER(("Not internable: %.*s\n", (int)len, str_)); */
    return 0;
  }
}

static int s2_cb_s2sh_line_read(cwal_callback_args const * args, cwal_value ** rv){
  char const * prompt = 0;
  char * line = 0;
  if(args->argc){
    if(!(prompt = cwal_value_get_cstr(args->argv[0], 0))){
      return s2_cb_throw(args, CWAL_RC_MISUSE,
                         "Expecting a string or no arguments.");
    }
  }else{
    prompt = "prompt >";
  }
  if(!(line = cliapp_lineedit_read(prompt))){
    *rv = cwal_value_undefined();
    return 0;
  }else{
    *rv = cwal_new_string_value(args->engine, line, cwal_strlen(line));
    cliapp_lineedit_free(line)
      /* if we knew with absolute certainty that args->engine is
         configured for the same allocator as the readline bits, we
         could use a Z-string and save a copy. But we don't, generically
         speaking, even though it's likely to be the same in all but the
         weirdest configurations. Even so, cwal may "massage" memory a
         bit to add some metadata, so we cannot pass it memory from
         another allocator, even if we know they both use the same
         underlying allocator.
      */;
    return *rv ? 0 : CWAL_RC_OOM;
  }
}

/**
   Callback implementation for s2_cb_s2sh_history_(load|add|save)().
*/
static int s2_cb_s2sh_history_las(cwal_callback_args const * args,
                                  cwal_value ** rv,
                                  char const * descr,
                                  int (*func)(char const * str) ){
  char const * str = args->argc
    ? cwal_value_get_cstr(args->argv[0], 0)
    : 0;
  if(!str){
    return s2_cb_throw(args, CWAL_RC_MISUSE,
                       "Expecting a string argument.");
  }
  else{
    int rc = (*func)(str);
    if(rc){
      rc = s2_cb_throw(args, rc,
                       "Native %s op failed with code %d/%s.",
                       descr, rc, cwal_rc_cstr(rc));
    }else{
      *rv = cwal_value_undefined();
    }
    return rc;
  }
}

static int s2_cb_s2sh_history_save(cwal_callback_args const * args, cwal_value ** rv){
  return s2_cb_s2sh_history_las( args, rv, "history-save", cliapp_lineedit_save );
}

static int s2_cb_s2sh_history_load(cwal_callback_args const * args, cwal_value ** rv){
  return s2_cb_s2sh_history_las( args, rv, "history-load", cliapp_lineedit_load);
}

static int s2_cb_s2sh_history_add(cwal_callback_args const * args, cwal_value ** rv){
  return s2_cb_s2sh_history_las( args, rv, "history-add",cliapp_lineedit_add);
}

/**
   cwal_value_visitor_f() impl. Internal helper for s2_dump_metrics().
*/
static int s2sh_metrics_dump_funcStash_keys( cwal_value * v, void * state ){
  cwal_size_t sz = 0;
  char const * n = cwal_value_get_cstr(v, &sz);
  if(state){/*unused*/}
  s2sh_print("\t%.*s\n", (int)sz, n);
  return 0;
}

static void s2_dump_metrics(s2_engine * se){
  printfEngine = se->e;
#define OUT(pfexp) s2sh_print pfexp
  if(App.verbosity){
    OUT(("\n"));
    OUT(("cwal-level allocation metrics:\n"));
    cwal_dump_allocation_metrics(se->e);
    OUT(("\n"));
    if(App.verbosity>2 && App.enableStringInterning){
      cwal_dump_interned_strings_table(se->e, 1, 32);
    }
  }
  if(se->metrics.tokenRequests){
    OUT(("s2-side metrics:\n"));
    OUT(("Peak cwal_scope level: %d with %d s2_scopes pushed/popped\n",
         se->metrics.maxScopeDepth,
         se->metrics.totalScopesPushed));
    OUT(("%d s2_scopes (sizeof %u) were alloc'd across "
         "%d (re)alloc(s) with a peak of %d bytes "
         "(counted among cwal_engine::memcap::currentMem).\n",
         (int)se->scopes.alloced,
         (unsigned int)sizeof(s2_scope),
         se->metrics.totalScopeAllocs,
         (int)(se->scopes.alloced * sizeof(s2_scope))));
    OUT(("Peak eval depth: %d\n",
         se->metrics.peakSubexpDepth));
    OUT(("Total s2_stokens (sizeof=%u) requested=%u, "
         "allocated=%u (=%u bytes), alive=%u, peakAlive=%u, "
         "currently in recycle bin=%d\n",
         (unsigned)sizeof(s2_stoken), se->metrics.tokenRequests,
         se->metrics.tokenAllocs, (unsigned)(se->metrics.tokenAllocs * (unsigned)sizeof(s2_stoken)),
         se->metrics.liveTokenCount, se->metrics.peakLiveTokenCount,
         se->recycler.stok.size));
    OUT(("Total calls to s2_next_token(): %u\n",
         se->metrics.nextTokenCalls));
    if(se->metrics.tokenAllocs < se->metrics.tokenRequests){
      OUT(("Saved %u bytes and %u allocs via stack token recycling :D.\n",
           (unsigned)((se->metrics.tokenRequests * (unsigned)sizeof(s2_stoken))
                      - (se->metrics.tokenAllocs * (unsigned)sizeof(s2_stoken))),
           se->metrics.tokenRequests - se->metrics.tokenAllocs
           ));
    }
  }
  OUT(("Scratch buffer (used by many String APIs): %u byte(s)\n",
       (unsigned)se->buffer.capacity));

  if(se->metrics.ukwdLookups){
    OUT(("se->metrics.ukwdLookups=%u, "
         "se->metrics.ukwdHits=%u\n",
         se->metrics.ukwdLookups,
         se->metrics.ukwdHits));
  }
  if(se->metrics.funcStateAllocs){
    OUT(("Allocated state for %u of %u script-side function(s) "
         "(sizeof %u), using %u bytes.\n",
         se->metrics.funcStateAllocs,
         se->metrics.funcStateRequests,
         s2_sizeof_script_func_state(),
         se->metrics.funcStateMemory));
  }
  if(se->funcStash){
    OUT(("Function script name hash: hash size=%d, entry count=%d. "
         "Re-used name count: %d\n",
         (int)cwal_hash_size(se->funcStash),
         (int)cwal_hash_entry_count(se->funcStash),
         (int)se->metrics.totalReusedFuncStash
         ));
    if(App.verbosity>2){
      OUT(("Function script name hash entries:\n"));
      cwal_hash_visit_keys(se->funcStash,
                           s2sh_metrics_dump_funcStash_keys, 0);
    }
  }
  if(se->stash){
    cwal_hash const * const h = cwal_value_get_hash(se->stash);
    OUT(("s2_engine::stash hash size=%d, entry count=%d\n",
         (int)cwal_hash_size(h), (int)cwal_hash_entry_count(h)));
  }
  if(se->metrics.assertionCount
     && (App.showMetrics || App.traceAssertions)
     ){
    OUT(("script-side 'assert' checks: %u\n",
         se->metrics.assertionCount));
  }
  OUT(("(end of s2 metrics)\n"));
  printfEngine = 0;
#undef OUT
}

static int s2sh_cb_dump_metrics(cwal_callback_args const * args, cwal_value ** rv){
  s2_engine * se = s2_engine_from_args(args);
  int const oldVerbosity = App.verbosity;
  int newVerbose = args->argc
    ? (int)cwal_value_get_integer(args->argv[0])
    : 0;
  App.verbosity = newVerbose;
  s2_dump_metrics(se);
  App.verbosity = oldVerbosity;
  *rv = cwal_value_undefined();
  return 0;
}

static int s2sh_report_engine_error( s2_engine * se ){
  int rc = 0;
  cwal_error * const err = &se->e->err;
  if(err->code || se->flags.interrupted){
    char const * msg = 0;
    rc = s2_engine_err_get(se, &msg, 0);
    if(err->line>0){
      fprintf(stderr,
              "s2_engine says error #%d (%s)"
              " @ script [%s], line %d, column %d: %s\n",
              rc, cwal_rc_cstr(rc),
              (char const *)err->script.mem,
              err->line, err->col, msg);
    }else{
      fprintf(stderr,
              "s2_engine says error #%d (%s)%s%s\n",
              rc, cwal_rc_cstr(rc),
              (msg && *msg) ? ": " : "",
              (msg && *msg) ? msg : "");
    }
  }
  return rc;
}

/**
   General-purpose result reporting function. rc should be the rc from
   the function we got *rv from. This function takes over
   responsibility of *rv from the caller (who should not take a
   reference to *rv). rv may be 0, as may *rv.

   After reporting the error, s2_engine_sweep() is run, which _might_
   clean up *rv (along with any other temporary values in the current
   scope).
*/
static int s2sh_report_result(s2_engine * se, int rc, cwal_value **rv ){
  char const * label = "result";
  cwal_value * rvTmp = 0;
  char showedMessage = 0;
  if(!rv) rv = &rvTmp;
  switch(rc){
    case 0: break;
    case CWAL_RC_RETURN:
      *rv = cwal_propagating_take(se->e);
      assert(*rv);
      break;
    case CWAL_RC_EXCEPTION:
      label = "EXCEPTION";
      *rv = cwal_exception_get(se->e);
      assert(*rv);
      cwal_value_ref(*rv);
      cwal_exception_set(se->e, 0);
      cwal_value_unhand(*rv);
      break;
    case CWAL_RC_EXIT:
      rc = 0;
      *rv = s2_propagating_take(se);
      break;
    case CWAL_RC_BREAK:
      label = "UNHANDLED BREAK";
      *rv = s2_propagating_take(se);
      break;
    case CWAL_RC_CONTINUE:
      label = "UNHANDLED CONTINUE";
      *rv = 0;
      break;
    case CWAL_RC_FATAL:
      label = "FATAL";
      *rv = s2_propagating_take(se);
      assert(*rv);
      assert(se->e->err.code);
      CWAL_SWITCH_FALL_THROUGH;
    default:
      ;
      if(s2sh_report_engine_error(se)){
        showedMessage = 1;
      }else{
        WARN(("Unusual result code (no error details): %d/%s\n",
              rc,cwal_rc_cstr2(rc)));
      }
      break;
  }
  if(*rv){
    assert((cwal_value_scope(*rv) || cwal_value_is_builtin(*rv))
           && "Seems like we've cleaned up too early.");
    if(rc || App.verbosity){
      if(rc && !showedMessage){
        fprintf(stderr,"rc=%d (%s)\n", rc, cwal_rc_cstr(rc));
      }
      s2_dump_value(*rv, label, 0, 0, 0);
    }
    /* We are uncertain of v's origin but want to clean it up if
       possible, so... */
#if 1
    /*
      Disable this and use -W to see more cleanup in action.  This
      ref/unref combo ensures that if it's a temporary, it gets
      cleaned up by this unref, but if it's not, then we effectively
      do nothing to it (because someone else is holding/containing
      it).
    */
    cwal_refunref(*rv);
#endif
    *rv = 0;
  }
  s2_engine_err_reset2(se);
  s2_engine_sweep(se);
  return rc;
}

static int s2_cb_s2sh_exit(cwal_callback_args const * args, cwal_value ** rv){
  if(args){/*avoid unused param warning*/}
  App.shellExitFlag = 1;
  *rv = cwal_value_undefined();
  return 0;
}

static int s2_install_shell_api( s2_engine * se, cwal_value * tgt,
                                 char const * name ){
  cwal_value * sub = 0;
  int rc = 0;
  if(!se || !tgt) return CWAL_RC_MISUSE;
  else if(!cwal_props_can(tgt)) return CWAL_RC_TYPE;

  if(name && *name){
    sub = cwal_new_object_value(se->e);
    if(!sub) return CWAL_RC_OOM;
    if( (rc = cwal_prop_set(tgt, name, cwal_strlen(name), sub)) ){
      cwal_value_unref(sub);
      return rc;
    }
  }else{
    sub = tgt;
  }

  {
    s2_func_def const funcs[] = {
    /* S2_FUNC2("experiment", s2_cb_container_experiment), */
    S2_FUNC2("tokenizeLine", s2_cb_tokenize_line),
    S2_FUNC2("readLine", s2_cb_s2sh_line_read),
    S2_FUNC2("historySave", s2_cb_s2sh_history_save),
    S2_FUNC2("historyLoad", s2_cb_s2sh_history_load),
    S2_FUNC2("historyAdd", s2_cb_s2sh_history_add),
    S2_FUNC2("exit", s2_cb_s2sh_exit),
    s2_func_def_empty_m
    };
    rc = s2_install_functions(se, sub, funcs, 0);
  }
#undef FUNC2
#undef SET
  return 0;
}

static int s2sh_run_file(s2_engine * se, char const * filename){
  cwal_value * xrv = 0;
  int rc = s2_eval_filename(se, 1, filename, -1, &xrv);
  return s2sh_report_result(se, rc, &xrv);
}

#if 0
cwal_value * s2_shell_global(){
  return App.s2Global;
}
#endif

/**
   Given a module name (via *name) and target object, this function
   creates any parent objects of that name, if needed. Parents are
   specified by using a underscore-delimited name. If the name
   contains a delimiter, each entry except that last one is treated as
   a parent, and they are created (if needed) recursively.

   On success:

   - *name is set to point to the tail part of the module name.
   e.g. the "foo" in "foo" and "bar" in "foo_bar".

   - *newTgt is set to new target container for the tail part of the
   module name (which this routine does not install - that's up to the
   caller).

   - 0 is returned.

   The only errors are allocation-related, so a non-0 return should be
   considered fatal.
*/
static int s2_shell_install_mod_parents( s2_engine * se,
                                         char const * separators,
                                         char const ** name,
                                         cwal_value * tgt,
                                         cwal_value **newTgt){
  int rc = 0;
  s2_path_toker pt = s2_path_toker_empty;
  char const * token = 0;
  int count = 0, i;
  cwal_size_t nT = 0, nName = cwal_strlen(*name);
  s2_path_toker_init(&pt, *name, (cwal_int_t)nName);
  pt.separators = separators;
  while(0==s2_path_toker_next(&pt, &token, &nT)){
    ++count;
  }
  assert(count>0);
  if(count>1){
    token = 0;
    nT = 0;
    s2_path_toker_init(&pt, *name, (cwal_int_t)nName);
    pt.separators = separators;
  }
  for(i = 0; i < count-1; ++i ){
    cwal_value * sub = 0;
    s2_path_toker_next(&pt, &token, &nT);
    sub = cwal_prop_get(tgt, token, nT);
    if(!sub){
      sub = cwal_new_object_value(se->e);
      if(!sub) return CWAL_RC_OOM;
      cwal_value_ref(sub);
      rc = cwal_prop_set(tgt, token, nT, sub);
      cwal_value_unref(sub);
      if(rc) return rc;
    }
    tgt = sub;
  }
  if(i==count-1){
    s2_path_toker_next(&pt, &token, &nT);
  }
  *name = token;
  *newTgt = tgt;
  return 0;
}

/**
   Internal helper for s2_setup_static_modules(). s2_module_init()'s
   the given module then sets its result (or wrapper object, for v1
   modules) to tgt[name]. Returns the result of the init call.

   If installName is not NULL and not the same as the given module
   name then it is assumed to be a dot-delimited list of properties
   (with 0 or more dots) as which the module should be installed
   in/under the given target. e.g. a name of "fooBarBaz" and
   installName of "foo.bar.baz" would install module fooBarBaz as
   tgt.foo.bar.baz. If the names are the same, or installName is NULL,
   the module is installed as tgt[name].

   As a compatibility crutch, if the installName and name differ, and
   installName installs sub-properties, this routine *also* installs
   the module as s2[name] for compatibilty with the numerous scripts
   which assuming a conventional module mapping of s2.moduleName.
*/
static int s2_shell_mod_init( s2_engine * se,
                              s2_loadable_module const * const mod,
                              char const * name,
                              char const * installName,
                              cwal_value * tgt){
  cwal_value * mrv = 0;
  int rc = 0;
  char const * originalName;
  char const * propName;
  char const * initLabel = "initialization";
#if 1 == S2SH_VERSION
  /* See comments below */
  cwal_value * const originalTgt = tgt;
#endif
  if(!name) name = mod->name;
  if(!installName) installName=name;
  originalName = name;
  propName = installName;
  if(strcmp(propName,name)){
    rc = s2_shell_install_mod_parents(se, ".", &propName, tgt, &tgt);
  }
  if(!rc) rc = s2_module_init(se, mod, &mrv);
  cwal_value_ref(mrv);
#if 1 == S2SH_VERSION
  top:
#endif
  if(!rc){
    rc = s2_set( se, tgt, propName, cwal_strlen(propName),
                 mrv ? mrv : cwal_value_undefined() );
  }else{
    assert(!mrv && "Module init must not return non-NULL on error.");
  }
  VERBOSE(2,("static module [%s%s%s] %s: %s\n",
             originalName,
             propName==installName ? "" : " ==> ",
             propName==installName ? "" : installName,
             initLabel,
             rc ? "FAILED" : "OK"));
#if 1 == S2SH_VERSION
  /**
     2020-01-31: kludge/crutch: we have many scripts which expect
     modules to be installed as s2[moduleName], but this code now
     supports installing modules as sub-properties, e.g. s2.regex.js
     instead of s2.regex_js. To keep the various scripts working, this
     check will install the module to its historical s2[moduleName]
     location if that was not where the first run-through of this
     function installed it.
  */
  if(!rc && originalTgt!=tgt){
    initLabel = "alias";
    installName = propName = name;
    tgt = originalTgt;
    goto top;
  }
#endif
  cwal_value_unref(mrv);
  return rc;
}

/**
   This holds a list of all module names which are being initialized
   via the "static module" process. We use this to give us a way to
   disable activation of built-in APIs which are being installed via
   their static module form.

   The names used here must be the canonical name of the module, not
   the "installation name". e.g. "regex_js" instead of "regex.js".
*/
static char const * const AppStaticModNames[] = {
#if defined(S2SH_MODULE_INITS)
#  define M(X) # X,
#  define M2(X,Y) " " # X,
S2SH_MODULE_INITS
#  undef M
#  undef M2
#endif
0 /* list sentinel */
};

/**
   A single-string form of AppModNames. It's exists only for
   showing the user in verbose mode.
*/
static char const * const AppStaticModNamesString = ""
#if defined(S2SH_MODULE_INITS)
#define M(X) " " # X
#define M2(X,Y) " " # Y
  S2SH_MODULE_INITS
#undef M
#undef M2
#endif
  ;

/**
   Returns true (non-0) if a static module with the name n was
   compiled in, else false (0).
*/
static int s2sh_has_static_mod( char const * n ){
  char const * const * p = AppStaticModNames;
  for( ; *p; ++p ){
    if(!strcmp(n,*p)) return 1;
  }
  return 0;
}
  
/**
   Initializes any static modules which this build knows about. Each
   one gets installed as tgt[X], where X is the module's name.
   Returns 0 on success. If App.initStaticModules is 0 then this
   is a no-op.
*/
static int s2_setup_static_modules( s2_engine * se, cwal_value * tgt ){
  int rc = 0;
  if(!App.initStaticModules) return 0;
  if(0){
    s2_shell_mod_init(0,0,0,0,tgt)
      /* We need a ref to this func in order for the func
         to be static, and there is otherwise no ref when building
         without statically-imported modules. */;      
  }
  /**
     Initializes the statically-linked module assocatiated with
     S2SH_MODULE_INIT_##MODNAME, installing it under the property
     named #INSTNAME.

     INSTNAME should either be the same as MODNAME or a
     "reformulation" which tells s2sh to install it under a different
     name, possibly somewhere other than the root of s2. e.g. passing
     (foo,bar.foo) would set up the installation of module foo as
     s2.bar.foo. INSTNAME must be a non-string token (this macro
     stringifies it).
  */
#define S2SH_MODULE_INIT2(MODNAME,INSTNAME)                             \
  {                                                                     \
    extern s2_loadable_module const * s2_module_ ## MODNAME;            \
    rc = s2_shell_mod_init(se, s2_module_ ## MODNAME, #MODNAME, #INSTNAME, tgt); \
    if(rc) goto end;                                                    \
  }
#define S2SH_MODULE_INIT(MODNAME)               \
  S2SH_MODULE_INIT2(MODNAME,MODNAME)

#if defined(S2SH_MODULE_INITS)
  /**
     Yet another approach to registering modules...

     Define S2SH_MODULE_INITS (note the trailing 'S') to a list
     in this form:

     M(module1) M(module2) ... M(moduleN)

     e.g.:

     cc -c shell.c ... '-DS2SH_MODULE_INITS=M(cgi) M(sqlite3)'

     Each M(X) will get translated to S2SH_MODULE_INIT(X), setting
     up registration of that module.

     Likewise, M2(X,Y) will be transformed the same way except that Y
     (which must not a quoted string (because we apparently can't(?)
     squeeze the required quotes through Make)) will be used for the
     property name, and a dot-delimited Y will cause the module to be
     installed in a sub-property of s2. e.g. M2(X,a.b.c) will install
     module X as s2.a.b.c, rather than s2.X.

     It is up to the user to link in the appropriate objects/libs for
     each module, as well as provide any linker flags or 3rd-party
     libraries those require.
  */
#  define M S2SH_MODULE_INIT
#  define M2 S2SH_MODULE_INIT2
  S2SH_MODULE_INITS
#  undef M
#  undef M2
#endif

#undef S2SH_MODULE_INIT
#undef S2SH_MODULE_INIT2
  
    if(rc) goto end /* avoid unused goto when no modules are built in */;
  end:
  if(rc){
    cwal_value * dummy = 0;
    s2sh_report_result(se, rc, &dummy)
      /* We do this to report any pending exception
         or se->err state. */;
  }
  return rc;
}

#if 0
static int s2_cb_dump_val(cwal_callback_args const * args, cwal_value ** rv){
  int i = 0;
  for( ; i < args->argc; ++i ){
    s2_dump_val(args->argv[i], "dump_val");
  }
  *rv = cwal_value_undefined();
  return 0;
}
#endif

static int s2sh_setup_s2_global( s2_engine * se ){
  int rc = 0;
  cwal_value * v = 0;
  cwal_engine * const e = se->e;
  cwal_value * s2 = 0;
#define VCHECK if(!v){rc = CWAL_RC_OOM; goto end;} (void)0
#define RC if(rc) goto end

  /*
    Reminder to self: we're not "leaking" anything on error here - it
    will be cleaned up by the scope when it pops during cleanup right
    after the error code is returned.
  */

  v = cwal_new_function_value( e, s2_cb_print, 0, 0, 0 );
  VCHECK;
  /*s2_dump_val(App.s2Global, "App.s2Global");*/
  cwal_value_ref(v);
#if 0
  if(2==S2SH_VERSION){
    rc = s2_define_ukwd(se, "if", 2, v);
    assert(CWAL_RC_ACCESS==rc);
    rc = s2_define_ukwd(se, "print", 5, v);
    RC;
    rc = s2_define_ukwd(se, "print", 5, v);
    assert(CWAL_RC_ALREADY_EXISTS==rc);
    rc = 0;
  }else
#endif
  {
    rc = cwal_var_decl(e, 0, "print", 5, v, 0);
    RC;
  }
  cwal_value_unref(v);

  /* Global s2 object... */
  s2 = v = cwal_new_object_value(e);
  VCHECK;
  cwal_value_ref(v);
  rc = cwal_var_decl(e, 0, "s2", 2, v, CWAL_VAR_F_CONST);
  if(!rc && 2==S2SH_VERSION){
    rc = s2_define_ukwd(se, "S2", 2, v);
  }
  cwal_value_unref(v);
  RC;
  App.s2Global = v;

#define SETV(K)                                     \
  VCHECK; cwal_value_ref(v);                        \
  rc = cwal_prop_set( s2, K, cwal_strlen(K), v );   \
  cwal_value_unref(v);                              \
  RC;

  v = s2_prototype_buffer(se);
  SETV("Buffer");
  v = s2_prototype_hash(se);
  SETV("Hash");

  v = s2_prototype_tuple(se);
  SETV("Tuple");
#undef SETV
    
  rc = s2_install_pf(se, s2);
  RC;

  if(AppStaticModNames[0]){
    VERBOSE(2,("Built-in static module list:%s\n",
               AppStaticModNamesString));
  }
  
  {
    /* Global functions must come before static module init because
       script-implemented modules often affirm/assert that a certain
       API is in place. */
    s2_func_def const funcs[] = {
    /*S2_FUNC2("newUnique", s2_cb_new_unique),*/
    /*S2_FUNC2("getResultCodeHash", s2_cb_rc_hash),*/
    /*S2_FUNC2("isCallable", s2_cb_is_callable),*/
    /*S2_FUNC2("isDerefable", s2_cb_is_derefable),*/
    S2_FUNC2("sealObject", s2_cb_seal_object),
    S2_FUNC2("loadModule", s2_cb_module_load),
    S2_FUNC2("minifyScript", s2_cb_minify_script),
#if 1==S2SH_VERSION
    /* s2.import is no longer needed but we very likely have scripts
       which still expect it. */
    S2_FUNC2("import", s2_cb_import_script),
#endif
    S2_FUNC2("glob", s2_cb_glob_matches_str),
    S2_FUNC2("getenv", s2_cb_getenv),
    /*S2_FUNC2("fork", s2_cb_fork),*/
    /*S2_FUNC2("dumpVal", s2sh_cb_dump_val),*/
    S2_FUNC2("dumpMetrics", s2sh_cb_dump_metrics),
    /*S2_FUNC2("compare", s2_cb_value_compare),*/
    S2_FUNC2("cwalBuildInfo", cwal_callback_f_build_info),
    s2_func_def_empty_m /* end-of-list sentinel */
    };
    if( (rc = s2_install_functions(se, s2,
                                   funcs, CWAL_VAR_F_CONST)) ) {
      goto end;
    }
  }

  /*
    Jump through some hoops to ensure that we don't install a built-in
    API which is getting statically linked in as a loadable module...

    For a module/API named N, if AppModNames contains N, we skip
    installation of that API because its module registration (which
    installs the same API) is pending. If AppModNames has no matching
    entry, we install the API with F(se, s2, N).
  */
#define MCHECK(N,F)                                             \
  if(!s2sh_has_static_mod(N)){                                  \
    if((rc = F(se, s2, N))) goto end;                           \
  }else{                                                        \
    VERBOSE(2,("Skipping install of [%s] API b/c "              \
               "there's a module with the same name.\n", N));   \
  }
  MCHECK("fs", s2_install_fs);
  MCHECK("io", s2_install_io);
  MCHECK("json",s2_install_json);
  MCHECK("ob", s2_install_ob_2);
  MCHECK("time",s2_install_time);
#undef MCHECK
#define MCHECK(N,F)                                                     \
  if(!s2sh_has_static_mod(N)){                                          \
    if((rc = s2_install_callback(se, s2, F, N, -1,                      \
                                 CWAL_VAR_F_CONST, 0, 0, 0))) goto end; \
  }else{                                                                \
    VERBOSE(2,("Skipping install of [%s] API b/c "                      \
               "there's a module with the same name.\n", N));           \
  }
  MCHECK("tmpl", s2_cb_tmpl_to_code);
#undef MCHECK

  if(App.enableInternalFu){
    rc = s2_install_callback(se, s2, s2_cb_internal_experiment,
                             "s2InternalExperiment", -1,
                             CWAL_VAR_F_CONST, 0, 0, 0);
    if(rc) goto end;
  }

  /* Some script-implemented modules rely on s2.ARGV for configuration,
     so it must be set up before modules are initialized. */
  v = cwal_new_array_value(e);
  cwal_value_ref(v);
  rc = cwal_parse_argv_flags(e, App.scriptArgc, App.scriptArgv, &v);
  if(rc){
    cwal_value_unref(v);
    goto end;
  }else{
    assert(v);
    rc = cwal_prop_set(s2, "ARGV", 4, v);
  }
  cwal_value_unref(v);
  v = 0;
  if(rc){
    goto end;
  }
  assert(!rc);

  if(1){
    rc = s2_setup_static_modules(se, s2);
    if(rc) goto end;
  }else{
      cwal_value * mod = cwal_new_object_value(se->e);
      if(!mod){
        rc = CWAL_RC_OOM;
        goto end;
      }
      cwal_value_ref(mod);
      rc = s2_set(se, s2, "mod", 3, mod);
      cwal_value_unref(mod);
      if(!rc) rc = s2_setup_static_modules(se, mod);
      if(rc) goto end;
  }

#if 0
  /*
    EXPERIMENTAL: i've always avoided making scope-owned variable
    storage directly visible to script code, but we're going to
    try this...

    Add global const __GLOBAL which refers directly to the variable
    storage of the top-most scope.

    If we like this, we'll change it to a keyword, which will make it
    many times faster to process.
  */
  v = cwal_scope_properties(cwal_scope_top(cwal_scope_current_get(e)))
    /* Cannot fail: we've already declared vars in this scope, so it's
       already allocated. */;
  assert(cwal_value_get_object(v));
  assert(cwal_value_refcount(v)>0 && "Because its owning scope holds a ref.");
  assert(0==cwal_value_prototype_get(e, v) && "Because cwal does not install prototypes for these!");
  cwal_value_prototype_set( v, s2_prototype_object(se) );
  if(cwal_value_is_hash(v)){
    s2_hash_dot_like_object(v, 1);
  }
  rc = cwal_var_decl(e, 0, "__GLOBAL", 8, v, CWAL_VAR_F_CONST)
    /* that effectively does: global.__GLOBAL = global */;
  RC;
#endif

#if 0
  { /* testing s2_enum_builder... */
    s2_enum_builder eb = s2_enum_builder_empty;
    cwal_value * v;
    char nameBuf[20] = {'e','n','t','r','y',0,0};
    cwal_int_t i;
    rc = s2_enum_builder_init(se, &eb, 17, "TestEnum");
    assert(!rc);
    RC;
    assert(cwal_value_is_vacuum_proof(eb.entries));
    for(i = 0; i < 3; ++i ){
      nameBuf[5] = '1' + i;
      v = cwal_new_integer(se->e, (i+1));
      VCHECK;
      cwal_value_ref(v);
      rc = s2_enum_builder_append(&eb, nameBuf, v);
      cwal_value_unref(v);
      v = 0;
    }
    rc = s2_enum_builder_seal(&eb, &v);
    assert(!rc);
    RC;
    assert(!eb.se);
    assert(!eb.entries);
    assert(v);
    assert(!cwal_value_is_vacuum_proof(v));
    assert(!cwal_value_refcount(v));
    cwal_value_ref(v);
    rc = cwal_var_decl(e, 0, "testEnum", 8, v, 0);
    cwal_value_unref(v);
    RC;
    MARKER(("testEnum (s2_enum_builder) installed! Try:\n"
            "foreach(testEnum=>k,v) print(k,v,v.value,testEnum[v])\n"));
  }
#endif
  
#undef FUNC2
#undef VCHECK
#undef RC
  end:
  return rc;
}

/**
   Callback for cliapp_repl().
*/
static int CliApp_repl_f_s2sh(char const * line, void * state){
  static cwal_hash_t prevHash = 0;
  cwal_size_t const nLine = cwal_strlen(line);
  if(nLine){
    s2_engine * const se = (s2_engine*)(state);
    cwal_value * v = 0;
    cwal_hash_t const lineHash = cwal_hash_bytes(line, nLine);
    int rc = 0;
    if(prevHash!=lineHash){
      /* Only add the line to the history if it seems to be different
         from the previous line. */
      cliapp_lineedit_add(line);
      prevHash = lineHash;
    }
    s2_set_interrupt_handlable( se );
    rc = s2_eval_cstr( se, 0, "shell input", line, (int)nLine, &v );
    s2_set_interrupt_handlable( 0 )
      /* keeps our ctrl-c handler from interrupting line reading or
         accidentially injecting an error state into se. */;
    s2sh_report_result(se, rc, &v);
    s2_engine_err_reset(se);
    if(App.shellExitFlag) return S2_RC_CLIENT_BEGIN;
  }
  return 0;
}

/**
   Enters an interactive-mode REPL loop if it can, otherwise it
   complains loudly about the lack of interactive editing and returns
   CWAL_RC_UNSUPPORTED.
*/
static int s2sh_interactive(s2_engine * se){
  int rc = 0;
  if(!cliApp.lineread.enabled){
      rc = s2_engine_err_set(se, CWAL_RC_UNSUPPORTED,
                             "Interactive mode not supported: "
                             "this shell was built without "
                             "line editing support.");
      s2sh_report_result(se, rc, 0);
      return rc;
  }

  cliapp_lineedit_load(NULL) /* ignore errors */;
  cwal_outputf(se->e, "s2 interactive shell. All commands "
               "are run in the current scope. Use your platform's EOF "
               "sequence on an empty line to exit. (Ctrl-D on Unix, "
               "Ctrl-Z(?) on Windows.) Ctrl-C might work, too.\n\n");
  rc = cliapp_repl( CliApp_repl_f_s2sh, &App.lnPrompt, 0, se );
  if(S2_RC_CLIENT_BEGIN==rc){
    /* Exit signal from the repl callback. */
    rc = 0;
  }
  cliapp_lineedit_save(NULL);
  s2_engine_sweep(se);
  cwal_output(se->e, "\n", 1);
  cwal_output_flush(se->e);
  return rc;
}


static int s2sh_main2( s2_engine * se ){
  int rc = 0;
  int i;
#if 0
  /* just testing post-init reconfig */
  {
    /* Configure the memory chunk recycler... */
    cwal_memchunk_config conf = cwal_memchunk_config_empty;
    conf.maxChunkCount = 0;
    conf.maxChunkSize = 1024 * 32;
    conf.maxTotalSize = 1024 * 256;
    conf.useForValues = 0;
    rc = cwal_engine_memchunk_config(se->e, &conf);
    assert(!rc);
  }
#endif

  if(!App.cleanroom){
    if(App.installShellApi>0
       || (App.interactive>0 && App.installShellApi<0)){
      assert(App.s2Global);
      if((rc = s2_install_shell_api(se, App.s2Global, "shell"))){
        return rc;
      }
    }
  }

  assert(App.scripts.nScripts <= S2SH_MAX_SCRIPTS);
  for( i = 0; i < App.scripts.nScripts; ++i ){
    char const * script = App.scripts.e[i].src;
    /*VERBOSE(2,("Running script #%d: %s\n", i+1, script));*/
    if(!*script) continue;
    if(App.scripts.e[i].isFile){
      rc = s2sh_run_file(se, script);
      cwal_output_flush(se->e);
      /* s2_engine_sweep(se); */
      if(rc) break;
    }else{
      cwal_value * rv = 0;
      rc = s2_eval_cstr( se, 0, "-e script",
                         script, cwal_strlen(script),
                         App.verbosity ? &rv : NULL );
      s2sh_report_result(se, rc, &rv);
      if(rc) break;
    }
  }
  /*MARKER(("Ran %d script(s)\n", i));*/
  if(!rc && App.interactive>0){
    rc = s2sh_interactive(se);
  }
  return rc;
}

/**
   If App.enableArg0Autoload and a file named $S2SH_INIT_SCRIPT or
   (failing that) the same as App.appName, minus any ".exe" or ".EXE"
   extension, but with an ".s2" extension, that script is loaded. If
   not found, 0 is returned. If loading or evaling the (existing) file
   fails, non-0 is returned and the error is reported.
*/
static int s2sh_autoload(s2_engine * se){
  int rc = 0;
  char const * scriptName = 0;
  if(!App.enableArg0Autoload || !App.appName || !*App.appName) return 0;
  else if(S2_DISABLE_FS_READ & s2_disable_get(se)){
    VERBOSE(1,("NOT loading init script because the --s2-disable=fs-read "
               "flag is active.\n"));
    return 0;
  }
  else if( !(scriptName = getenv("S2SH_INIT_SCRIPT")) ){
    /*
      See if $0.s2 exists...

      FIXME: this doesn't work reliably if $0 is relative, e.g.
      found via $PATH. We need to resolve its path to do this
      properly, but realpath(3) is platform-specific.
    */
    enum { BufSize = 1024 * 3 };
    static char fn[BufSize] = {0};
    cwal_size_t slen = 0;
    char const * exe = 0;
    char const * arg0 = App.appName;
    assert(arg0);
    exe = strstr(arg0, ".exe");
    if(!exe) exe = strstr(arg0, ".EXE");
    if(!exe) exe = strstr(arg0, ".Exe");
    slen = cwal_strlen(arg0) - (exe ? 4 : 0);
    rc = sprintf(fn, "%.*s.s2", (int)slen, arg0);
    assert(rc<BufSize-1);
    rc = 0;
    scriptName = fn;
  }
  if(s2_file_is_accessible(scriptName, 0)){
    if(App.cleanroom){
      VERBOSE(1,("Clean-room mode: NOT auto-loading script [%s].\n", scriptName));
    }else{
      cwal_value * rv = 0;
      VERBOSE(2,("Auto-loading script [%s].\n", scriptName));
      rc = s2_eval_filename(se, 1, scriptName, -1, &rv);
      if(rc){
        MESSAGE(("Error auto-loading init script [%s]: "
                 "error #%d (%s).\n", scriptName,
                 rc, cwal_rc_cstr(rc)));
        s2sh_report_result(se, rc, &rv);
      }
      else{
        cwal_refunref(rv);
      }
    }
  }
  return rc;
}

int s2sh_main(int argc, char const * const * argv){
  enum {
  /* If either of these is 0 then the corresponding engine gets
     allocated dynamically. In practice there's generally no reason to
     prefer dynamic allocation over stack allocation. */
  UseStackCwalEngine = 1 /* cwal_engine */,
  UseStackS2Engine = 1 /* s2_engine */
  };
  cwal_engine E = cwal_engine_empty;
  cwal_engine * e = UseStackCwalEngine ? &E : 0;
  cwal_engine_vtab vtab = cwal_engine_vtab_basic;
  s2_engine SE = s2_engine_empty;
  s2_engine * se = UseStackS2Engine ? &SE : 0;
  int rc = 0;

  if(argc || argv){/*possibly unused var*/}
  setlocale(LC_CTYPE,"") /* supposedly important for the JSON-parsing bits? */;

  vtab.tracer = cwal_engine_tracer_FILE;
  vtab.tracer.state = stdout;
  vtab.hook.on_init = s2sh_init_engine;
  vtab.interning.is_internable = cstr_is_internable;
  vtab.outputer.state.data = stdout;
  assert(cwal_finalizer_f_fclose == vtab.outputer.state.finalize);

  if(App.outFile){
    FILE * of = (0==strcmp("-",App.outFile))
      ? stdout
      : fopen(App.outFile,"wb");
    if(!of){
      rc = CWAL_RC_IO;
      MARKER(("Could not open output file [%s].\n", App.outFile));
      goto end;
    }
    else {
      vtab.outputer.state.data = of;
      vtab.outputer.state.finalize = cwal_finalizer_f_fclose;
      vtab.tracer.state = of;
      vtab.tracer.close = NULL
        /* Reminder: we won't want both outputer and tracer
           to close 'of'.
        */;
    }
  }

  vtab.memcap = App.memcap;

  rc = cwal_engine_init( &e, &vtab );
  if(rc){
    cwal_engine_destroy(e);
    goto end;
  }

  se = UseStackS2Engine ? &SE : s2_engine_alloc(e);
  assert(se);
  assert(UseStackS2Engine ? !se->allocStamp : (se->allocStamp == e));

  if((rc = s2_engine_init( se, e ))) goto end;
  if(App.cleanroom){
    VERBOSE(1,("Clean-room mode: NOT installing globals/prototypes.\n"));
  }else{
    if((rc = s2_install_core_prototypes(se))) goto end;
    else if( (rc = s2sh_setup_s2_global(se)) ) goto end;
  }
  if(App.zDisableFlags){
    rc = s2_disable_set_cstr(se, App.zDisableFlags, -1, 0);
    if(rc) goto end;
  }
  se->sweepInterval += App.addSweepInterval;
  se->vacuumInterval += App.addVacuumInterval;
  se->flags.traceTokenStack = App.traceStacks;
  se->flags.traceAssertions = App.traceAssertions;
  se->flags.traceSweeps = App.traceSweeps;
  s2_set_interrupt_handlable( se )
    /* after infrastructure, before the auto-loaded script, in case it
       contains an endless loop or some such and needs to be
       interrupted.
    */;
#if defined(S2_SHELL_EXTEND)
  if(!App.cleanroom){
    if( (rc = S2_SHELL_EXTEND_FUNC_NAME(se, App.s2Global, argc, argv)) ) goto end;
  }else{
    VERBOSE(1,("Clean-room mode: NOT running client-side shell extensions.\n"));
  }
#endif

  if(App.cleanroom){
    VERBOSE(1,("Clean-room mode: NOT auto-loading init script.\n"));
  }else{
    rc=s2sh_autoload(se);
  }
  s2_set_interrupt_handlable( 0 );
  if(rc) goto end;
       
  rc = s2sh_main2(se);
  App.s2Global = 0;

  end:
  s2_set_interrupt_handlable( 0 );
  if(App.showMetrics){
    s2_dump_metrics(se);
  }
#if 1
  s2sh_report_engine_error(se);
#endif
  assert(!se->st.vals.size);
  assert(!se->st.ops.size);
  s2_engine_finalize(se);
  return rc;
}


/**
   Pushes scr to App.scripts if there is space for it, else outputs an
   error message and returns non-0. isFile must be true if scr refers
   to a filename, else it is assumed to hold s2 script code.
*/
static int s2sh_push_script( char const * scr, int isFile ){
  const int n = App.scripts.nScripts;
  if(S2SH_MAX_SCRIPTS==n){
    MARKER(("S2SH_MAX_SCRIPTS (%d) exceeded: give fewer scripts or "
            "rebuild with a higher S2SH_MAX_SCRIPTS.\n",
            S2SH_MAX_SCRIPTS));
    return CWAL_RC_RANGE;
  }
  App.scripts.e[n].isFile = isFile;
  App.scripts.e[n].src = scr;
  VERBOSE(3,("Pushed %s #%d: %s\n", isFile ? "file" : "-e script",
             n+1, App.scripts.e[n].src));
  ++App.scripts.nScripts;
  return 0;
}

static void s2sh_help_header_once(){
  static int once = 0;
  if(once) return;
  once = 1;
  cliapp_print("s2 scripting engine shell #%d\n",
               S2SH_VERSION);
}

static void s2sh_show_version(int detailedMode){
  s2sh_help_header_once();
  cliapp_print("cwal/s2 version: %s\n",
               cwal_build_info()->versionString);
  cliapp_print("Project site: https://fossil.wanderinghorse.net/r/cwal\n");
  if(detailedMode){
    cwal_build_info_t const * const bi = cwal_build_info();
    cliapp_print("\nlibcwal compile-time info:\n\n");
#define STR(MEM) cliapp_print("\t%s = %s\n", #MEM, bi->MEM)
    /*STR(versionString);*/
    STR(cppFlags);
    STR(cFlags);
    STR(cxxFlags);
#undef STR

#define SZ(MEM) cliapp_print("\t%s = %"CWAL_SIZE_T_PFMT"\n", #MEM, (cwal_size_t)bi->MEM)
    SZ(size_t_bits);
    SZ(int_t_bits);
    SZ(maxStringLength);
#undef SZ

#define BUL(MEM) cliapp_print("\t%s = %s\n", #MEM, bi->MEM ? "true" : "false")
    BUL(isJsonParserEnabled);
    BUL(isDebug);
#undef BUL

    cliapp_print("\n");
  }
}

/**
   Parses and returns the value of an unsigned int CLI switch. Assigns
   *rc to 0 on success, non-0 on error.
*/
static cwal_size_t s2sh_parse_switch_unsigned(CliAppSwitch const * s, int * rc){
  cwal_int_t i = 0;
  if(!s2_cstr_parse_int(s->value, -1, &i)){
    WARN(("-cap-%s must have a positive integer value.\n", s->key));
    *rc = CWAL_RC_MISUSE;
    return 0;
  }else if(i<0){
    WARN(("-cap-%s must have a positive value (got %d).\n", s->key, (int)i));
    *rc = CWAL_RC_RANGE;
    return 0;
  }else{
    *rc = 0;
    return (cwal_size_t)i;
  }
}


/**
   The help handler for a single CliAppSwitch. It requires that all
   switches are passed to it in the order they are defined at the app
   level, as it applies non-inuitive semantics to certain entries,
   e.g. collapsing -short and --long-form switches into a single
   entry.
*/
static int s2sh_help_switch_visitor(CliAppSwitch const *s,
                                    void * /* ==>int*  */ verbosity){
  static CliAppSwitch const * skip = 0;
  CliAppSwitch const * next = s+1
    /* this is safe b/c the list ends with an empty sentinel entry */;
  int group = 0;
  static int prevContinue = 0;
  if(s->opaque & SW_UNDOCUMENTED){
    return 0;
  }else if(skip == s){
      skip = 0;
      return 0;
  }
  switch(s->opaque & SW_GROUPS){
    case SW_GROUP_0: break;
    case SW_GROUP_1: group = 1; break;
    case SW_GROUP_2: group = 2; break;
  }
  assert(s->dash>=-1 && s->dash<=2);
  assert(group>=0 && group<3);
  /*MARKER(("group %d (%d) %s\n", group, *((int const*)verbosity), s->key));*/
  if(group>*((int const*)verbosity)){
      return CWAL_RC_BREAK;
  }
  if(s->opaque & SW_GROUPS){
    /* Use s->brief as a label for this section then return. */
    if(s->brief && *s->brief){
      char const * splitter = "===============================";
      cliapp_print("\n%s\n%s\n%s\n", splitter, s->brief, splitter);
    }
    return 0;
  }
  if((s->opaque & SW_ID_MASK) && (next->opaque==s->opaque)){
    /* Collapse long/short options into one entry. We expect the long
       entry to be the first one. */
    /* FIXME? This only handles collapsing 2 options, but s2sh
       help supports -?/-help/--help. */
      skip = next;
      cliapp_print("\n  %s%s | ", cliapp_flag_prefix(skip->dash),
                   skip->key);
  }else if(!next->key){
      skip = 0;
  }
  /*MARKER(("switch entry: brief=%s, details=%s\n", s->brief, s->details));*/
  if(s->key && *s->key){
    cliapp_print("%s%s%s%s%s%s%s\n",
                 skip ? "" : "\n  ",
                 cliapp_flag_prefix(s->dash), s->key,
                 s->value ? " = " : "",
                 s->value ? s->value : "",
                 s->brief ? "\n      ": "",
                 s->brief ? s->brief : "");
    prevContinue = s->opaque;
  }else{
    switch(s->opaque){
      case SW_CONTINUE:
        assert(s->brief && *s->brief);
        if(prevContinue==SW_INFOLINE){
          cliapp_print("%s\n", s->brief);
        }else{
          cliapp_print("      %s\n", s->brief);
        }
        break;
      case SW_INFOLINE:
        assert(s->brief && *s->brief);
        cliapp_print("\n%s\n", s->brief);
        prevContinue = s->opaque;
        break;
      default:
        prevContinue = s->opaque;
        break;
    }
  }
  return 0;
}

static void s2sh_show_help(){
  int maxGroup = App.verbosity;
  char const * splitter = 1 ? "" :
    "------------------------------------------------------------\n";
  int n = 0;
  s2sh_show_version(0);
  cliapp_print("\nUsage: %s [options] [file | -f file]\n",
               App.appName);
  cliapp_print("%s",splitter);
  cliapp_switches_visit( s2sh_help_switch_visitor, &maxGroup );
  cliapp_print("\n%sNotes:\n",splitter); 
  cliapp_print("\n%d) Flags which require a value may be provided as (--flag=value) or "
        "(--flag value).\n", ++n);
  cliapp_print("\n%d) All flags after -- are ignored by the shell but "
               "made available to script code via the s2.ARGV object.\n", ++n);
  cliapp_print("Minor achtung: the script-side arguments parser uses --flag=VALUE, "
               "whereas this app's flags accept (--flag VALUE)!\n");

  if(maxGroup<2){
      cliapp_print("\n%d) Some less obscure options were not shown. "
                   "Use -v once or twice with -? for more options.\n", ++n);
  }
  puts("");
}

static int s2shGotHelpFlag = 0;
static int s2sh_arg_callback_common(int ndx, CliAppSwitch const * s,
                                    CliAppArg * a){
  int const sFlags = s ? s->opaque : 0;
  int const sID = sFlags & SW_ID_MASK;
  int rc = 0;
  if(0 && a){
    MARKER(("switch callback sFlags=%06x, sID=%d, SW_ESCRIPT=%d: #%d %s%s%s%s\n",
            sFlags, sID, SW_ESCRIPT,
            ndx, cliapp_flag_prefix(a->dash), a->key,
            a->value ? "=" : "",
            a->value ? a->value : ""));
  }
  if(!a/* end of args - there's nothing for use to do in this case */){
    return 0;
  }
  switch(sID){
    case SW_ASSERT_0: App.traceAssertions = 0; break;
    case SW_ASSERT_1: ++App.traceAssertions; break;
    case SW_AUTOLOAD_0: App.enableArg0Autoload = 0; break;
    case SW_AUTOLOAD_1: App.enableArg0Autoload = 1; break;
    case SW_CLAMPDOWN:
      WARN(("Clampdown mode has been removed."));
      return CWAL_RC_UNSUPPORTED;
    case SW_CLEANROOM: App.cleanroom = 1; break;
    case SW_DISABLE: App.zDisableFlags = a->value; break;
    case SW_ESCRIPT:
      assert(a->value && "Should have been captured by cliapp.");
      rc = s2sh_push_script( a->value, 0 );
      if(App.interactive<0) App.interactive=0;
      break;
    case SW_HELP: ++s2shGotHelpFlag; break;
    case SW_HIST_FILE_1: cliApp.lineread.historyFile = a->value; break;
    case SW_HIST_FILE_0: cliApp.lineread.historyFile = 0; break;
    case SW_INFILE:
      assert(a->value && "Should have been captured by cliapp.");
      rc = s2sh_push_script( a->value, 1 );
      if(!rc) App.inFile = a->value;
      break;
    case SW_INTERACTIVE: App.interactive = 1; break;
    case SW_INTERNAL_FU: ++App.enableInternalFu; break;
    case SW_MEM_FAST0: App.memcap.forceAllocSizeTracking = 0; break;
    case SW_MEM_FAST1: App.memcap.forceAllocSizeTracking = 1; break;
    case SW_MCAP_TOTAL_ALLOCS:
      App.memcap.maxTotalAllocCount =
        s2sh_parse_switch_unsigned(s, &rc);
      break;
    case SW_MCAP_TOTAL_BYTES:
      App.memcap.maxTotalMem = s2sh_parse_switch_unsigned(s, &rc);
      break;
    case SW_MCAP_CONC_ALLOCS:
      App.memcap.maxConcurrentAllocCount =
        s2sh_parse_switch_unsigned(s, &rc);
      break;
    case SW_MCAP_CONC_BYTES:
      App.memcap.maxConcurrentMem = s2sh_parse_switch_unsigned(s, &rc);
      break;
    case SW_MCAP_SINGLE_BYTES:
      App.memcap.maxSingleAllocSize =
        s2sh_parse_switch_unsigned(s, &rc);
      break;
    case SW_METRICS: App.showMetrics = 1; break;
    case SW_MOD_INIT_0: App.initStaticModules = 0; break;
    case SW_MOD_INIT_1: App.initStaticModules = 1; break;
    case SW_OUTFILE: App.outFile = a->value; break;
    case SW_RE_C0: App.maxChunkCount = 0; break;
    case SW_RE_C1: App.maxChunkCount = 30; break;
    case SW_RE_S0: App.enableStringInterning = 0; break;
    case SW_RE_S1: App.enableStringInterning = 1; break;
    case SW_RE_V0: App.enableValueRecycling = 0; break;
    case SW_RE_V1: App.enableValueRecycling = 1; break;
    case SW_SCOPE_H0: App.scopesUseHashes = 0; break;
    case SW_SCOPE_H1: App.scopesUseHashes = 1; break;
    case SW_SHELL_API_0: App.installShellApi = 0; break;
    case SW_SHELL_API_1: App.installShellApi = 1; break;
    case SW_TRACE_CWAL:
      App.cwalTraceFlags = CWAL_TRACE_SCOPE_MASK
        | CWAL_TRACE_VALUE_MASK
        | CWAL_TRACE_MEM_MASK
        | CWAL_TRACE_FYI_MASK
        | CWAL_TRACE_ERROR_MASK;
      break;
    case SW_TRACE_STACKS_0: App.traceStacks = 0; break;
    case SW_TRACE_STACKS_1: ++App.traceStacks; break;
    case SW_TRACE_SWEEP_0: App.traceSweeps = 0; break;
    case SW_TRACE_SWEEP_1: ++App.traceSweeps; break;
    case SW_VERBOSE: ++App.verbosity; break;
    case SW_SHOW_SIZEOFS:
    default: break;
  }
  return rc;
}

static int dump_args_visitor(CliAppArg const * p, int i, void * state){
  if(state){/*unused*/}
  assert(p->key);
  cliapp_print("argv[%d]  %s%s%s%s\n",i,
               cliapp_flag_prefix(p->dash), p->key,
               p->value ? "=" : "",
               p->value ? p->value : "");
  return 0;
}

static void s2sh_dump_args(){
  puts("CLI arguments:");
  cliapp_args_visit(dump_args_visitor, 0, 1)
    /* Over-engineering, just to test that cliapp code. */;
}

int main(int argc, char const * const * argv){
  int rc = 0;
  char const * arg = 0;
  int gotNoOp = 0
    /* incremented when we get a "no-op" flag. These are
       flags which perform some work without invoking the
       interpreter, and this flag lets us distinguish between
       not getting any options and getting some which did
       something useful (--help, --version, etc)*/;

  s2_static_init();
  App.appName = argv[0];
  cliApp.switches = s2sh_cliapp_switches();
  cliApp.argCallack = s2sh_arg_callback_common;
  assert(!App.shellExitFlag);
  memset(&App.scripts,0,sizeof(App.scripts));
  App.memcap.forceAllocSizeTracking = 0;
  
  if((arg = getenv("S2SH_HISTORY_FILE"))){
    App.editHistoryFile = arg;
  }
  cliApp.lineread.historyFile = App.editHistoryFile;

  rc = cliapp_process_argv(argc, argv, 0);
  if(rc) rc = CWAL_RC_MISUSE
           /* It's *possibly* not a CWAL_RC_ code, to we assume misuse. */;
  if(rc) goto end;
  App.scriptArgc = cliApp.doubleDash.argc;
  App.scriptArgv = cliApp.doubleDash.argv;

  if(0){
    CliAppArg const * p;
    if(0){
      if(!rc){
        s2sh_dump_args();
        while((p=cliapp_arg_nonflag())){
          MARKER(("Non-flag arg: %s\n",p->key));
        }
        cliapp_arg_nonflag_rewind();
      }
    }
  }

  if(s2shGotHelpFlag){
    s2sh_show_help();
    gotNoOp = 1;
  }else if(cliapp_arg_flag("V","version", 0)){
    s2sh_show_version(App.verbosity);
    gotNoOp = 1;
  }else if(!App.inFile){
      /* Treat the first non-flag arg as an -f flag if no -f was
         explicitely specified. */
    CliAppArg const * ca;
    assert(argc>1 ? cliApp.cursorNonflag>0 : 1);
    ca = cliapp_arg_nonflag();
    if(ca){
      rc = s2sh_push_script( ca->key, 1 );
      if(rc) goto end;
      else App.inFile = ca->key;
    }
  }
#if 1 == S2SH_VERSION
  if(cliapp_arg_flag("z",0,0)){
    gotNoOp = 1;
#define SO(T) MESSAGE(("sizeof("#T")=%d\n", (int)sizeof(T)))
    SO(s2_stoken);
    SO(s2_ptoken);
    SO(s2_ptoker);
    /* SO(s2_op); */
    SO(s2_scope);
    SO(cwal_scope);
    SO(s2_engine);
    SO(cwal_engine);
    SO(cwal_memchunk_overlay);
    SO(cwal_list);
#undef SO
    MESSAGE(("sizeof(s2_func_state)=%d\n",
             (int)s2_sizeof_script_func_state()));
  }
#endif /* -z flag */

  if(!gotNoOp && App.inFile && cliapp_arg_nonflag()){
    WARN(("Extraneous non-flag arguments provided.\n"));
    rc = CWAL_RC_MISUSE;
    goto end;
  }

  assert(!rc);
  if(!gotNoOp){
    if(!App.inFile && App.interactive<0
       && !App.scripts.nScripts){
#ifdef S2_OS_UNIX
      if(isatty(STDIN_FILENO)){
        App.interactive = 1;
      }else App.inFile = "-";
#else
      App.inFile = "-";
#endif
    }
    rc = s2sh_main(argc, argv);
  }

  end:
  {
    char const * errMsg = cliapp_err_get();
    if(errMsg){
      WARN(("%s\n", errMsg));
    }
  }
  if(App.verbosity>0 && rc){
    WARN(("rc=%d (%s)\n", rc, cwal_rc_cstr(rc)));
  }
  return rc ? EXIT_FAILURE : EXIT_SUCCESS;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






















































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted bindings/s2/shell_extend.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/**
   This file is meant to act as a client-side way to extend the default
   s2 shell app. It demonstrates examples of connecting C code to
   the s2 scripting environment.

   If shell.c is built with S2_SHELL_EXTEND defined, then it will assume
   this function will be linked in by the client:

   int s2_shell_extend(s2_engine * se, int argc, char const * const * argv);

   When built with the macro S2_SHELL_EXTEND, it calls that function right
   after it installs its own core functionality.
*/
#include <assert.h>
#if defined(S2_AMALGAMATION_BUILD)
#  include "s2_amalgamation.h"
#else
#  include "s2.h"
#endif
#include "fossil-scm/fossil.h"

#ifdef S2_OS_UNIX
# include <unistd.h> /* F_OK and friends */
#elif defined(S2_OS_WINDOWS)
# include <windows.h>
#endif

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


/**
   Type IDs for type-safely mapping (void*) to cwal_native
   instances. Their types and values are irrelevant - we use only their
   addresses.
*/
static const int cwal_type_id_fsl_cx = 0;
static const int cwal_type_id_fsl_db = 0;
static const int cwal_type_id_fsl_stmt = 0;
#define FSL_TYPEID(X) (&cwal_type_id_##X)

#define VERBOSE_FINALIZERS 0

static void cwal_finalizer_f_fsl_db( cwal_engine * e, void * m ){
  if(m){
#if VERBOSE_FINALIZERS
    MARKER(("Finalizing fsl_db @%p\n", m));
#endif
    fsl_db_close( (fsl_db*)m );
  }
}

void cwal_finalizer_f_fsl_stmt( cwal_engine * e, void * m ){
  if(m){
#if VERBOSE_FINALIZERS
    MARKER(("Finalizing fsl_stmt @%p\n", m));
#endif
    fsl_stmt_finalize( (fsl_stmt*)m );
  }
}

static void cwal_finalizer_f_fsl_cx( cwal_engine * e, void * m ){
  if(m){
#if VERBOSE_FINALIZERS
    MARKER(("Finalizing fsl_cx @%p\n", m));
#endif
    fsl_cx_finalize( (fsl_cx*)m );
  }
}

#undef VERBOSE_FINALIZERS

static int cb_toss( cwal_callback_args const * args, int code,
                    char const * fmt, ... ){
  int rc;
  va_list vargs;
  s2_engine * se = s2_engine_from_args(args);
  cwal_native * nat = cwal_value_native_part(args->engine, args->self,
                                             FSL_TYPEID(fsl_cx) );
  cwal_value * natV = nat ? cwal_native_value(nat) : NULL;
  fsl_cx * f = natV ? cwal_native_get( nat, FSL_TYPEID(fsl_cx) ) : NULL;
  assert(se);
  va_start(vargs,fmt);
  rc = cwal_exception_setfv(args->engine, code, fmt, vargs );
  if(f){
    /* Ensure that script-triggered errors do not unduly
       lie around, potentially propagating long after
       they are valid.
    */
    fsl_cx_err_reset(f);
  }
  va_end(vargs);
  return rc;
}

static int cb_toss_fsl( cwal_callback_args const * args,
                        fsl_cx * f ){
  int rc;
  assert(f && f->error.code);
  rc = (FSL_RC_OOM==f->error.code)
    ? CWAL_RC_OOM
    : cb_toss(args,
              f->error.code,
              "%.*s", (int)f->error.msg.used,
              (char const *)f->error.msg.mem );
  fsl_cx_err_reset(f);
  return rc;
}

static int cb_toss_db( cwal_callback_args const * args,
                       fsl_db * db ){
  int rc;
  assert(db && db->error.code);
  rc = (FSL_RC_OOM==db->error.code)
    ? CWAL_RC_OOM
    : cb_toss(args, db->error.code,
              "%s", (char const *)db->error.msg.mem );
  fsl_db_err_reset(db);
  return rc;
}

static cwal_value * fsl_cx_prototype( s2_engine * se );
static cwal_value * fsl_db_prototype( s2_engine * se );
static cwal_value * fsl_stmt_prototype( s2_engine * se );

/*
** Copies fb's state into cb.
*/
static int fsl_buffer_to_cwal_buffer( cwal_engine * e,
                                      fsl_buffer const * fb,
                                      cwal_buffer * cb ){
  cb->used = 0;
  return cwal_buffer_append(e, cb, fb->mem, (cwal_size_t)fb->used);
}

#if 0
/*
** Shallowly copies cb's state into fb. fb MUST NOT be modified
** via the fsl allocator while this is in place, because cwal's
** allocator is not guaranteed to be compatible (and won't be
** if certain memory capping options are enabled).
*/
static void cwal_buffer_to_fsl_buffer( cwal_buffer const * cb,
                                       fsl_buffer * fb ){
  fb->mem  = cb->mem;
  fb->capacity = cb->capacity;
  fb->used = cb->used;
  fb->cursor = 0;
}
#endif

/**
   Script usage:

   const sha1 = bufferInstance.sha1()
*/
static int cb_buffer_sha1_self( cwal_callback_args const * args,
                                cwal_value **rv ){
  cwal_buffer * buf = cwal_value_buffer_part(args->engine, args->self);
  char * sha;
  if(!buf){
    return cb_toss(args, CWAL_RC_TYPE,
                   "'this' is-not-a Buffer.");
  }
  sha = fsl_sha1sum_cstr( (char const*)buf->mem, (int)buf->used);
  if(!sha) return CWAL_RC_OOM;
  assert(sha[0] && sha[FSL_STRLEN_SHA1-1] && !sha[FSL_STRLEN_SHA1]);
  *rv = cwal_new_string_value(args->engine, sha, FSL_STRLEN_SHA1);
  /* Reminder: can't use zstring here b/c of potentially different
     allocators. */
  fsl_free(sha);
  if(*rv) return 0;
  else{
    return CWAL_RC_OOM;
  }
}
static int cb_buffer_sha3_self( cwal_callback_args const * args,
                                cwal_value **rv ){
  cwal_buffer * buf = cwal_value_buffer_part(args->engine, args->self);
  char * sha;
  if(!buf){
    return cb_toss(args, CWAL_RC_TYPE,
                   "'this' is-not-a Buffer.");
  }
  sha = fsl_sha3sum_cstr( (char const*)buf->mem, (int)buf->used);
  if(!sha) return CWAL_RC_OOM;
  assert(sha[0] && sha[FSL_STRLEN_K256-1] && !sha[FSL_STRLEN_K256]);
  *rv = cwal_new_string_value(args->engine, sha, FSL_STRLEN_K256);
  /* Reminder: can't use zstring here b/c of potentially different
     allocators. */
  fsl_free(sha);
  if(*rv) return 0;
  else{
    return CWAL_RC_OOM;
  }
}

/**
   Script usage:

   const md5 = bufferInstance.md5()
*/
static int cb_buffer_md5_self( cwal_callback_args const * args,
                               cwal_value **rv ){
  cwal_buffer * buf = cwal_value_buffer_part(args->engine, args->self);
  char * hash;
  if(!buf){
    return cb_toss(args, CWAL_RC_TYPE,
                   "'this' is-not-a Buffer.");
  }
  hash = fsl_md5sum_cstr( (char const*)buf->mem, (int)buf->used);
  if(!hash) return CWAL_RC_OOM;
  assert(hash[0] && hash[FSL_STRLEN_MD5-1] && !hash[FSL_STRLEN_MD5]);
  *rv = cwal_new_string_value(args->engine, hash, FSL_STRLEN_MD5);
  fsl_free(hash);
  if(*rv) return 0;
  else{
    return CWAL_RC_OOM;
  }
}

/*
** fsl_output_f() impl which forwards to cwal_output().
** state must be a (cwal_engine *). Reminder: must return
** a FSL_RC_xxx value, not CWAL_RC_xxx.
*/
static int fsl_output_f_cwal_output( void * state,
                                     void const * src,
                                     fsl_size_t n ){
  cwal_engine * e = (cwal_engine *)state;
  return cwal_output( e, src, (cwal_size_t)n )
    ? FSL_RC_IO
    : 0;
}

/*
** fsl_flush_f() impl which forwards to cwal_output_flush(). state
** must be a (cwal_engine *).
*/
static int fsl_flush_f_cwal_out( void * state ){
  cwal_engine * e = (cwal_engine *)state;
  return cwal_output_flush(e);
}

static int s2_fsl_openmode_to_flags(char const * openMode, int startFlags){
  int openFlags = startFlags;
  for( ; *openMode; ++openMode ){
    switch(*openMode){
      case 'r': openFlags |= FSL_OPEN_F_RO;
        break;
      case 'w': openFlags |= FSL_OPEN_F_RW;
        break;
      case 'c': openFlags |= FSL_OPEN_F_CREATE;
        break;
      case 'v': openFlags |= FSL_OPEN_F_SCHEMA_VALIDATE;
        break;
      case 'T': openFlags |= FSL_OPEN_F_TRACE_SQL;
        break;
      default:
        break;
    }
  }
  return openFlags;
}

/*
** Searches v and its prototype chain for a fsl_cx binding. If found,
** it is returned, else NULL is returned.
*/
static fsl_cx * cwal_value_fsl_cx_part( cwal_engine * e,
                                        cwal_value * v ){
  if(!v) return NULL;
  else {
    cwal_native * nv;
    fsl_cx * f;
    while(v){
      nv = cwal_value_get_native(v);
      f = nv
        ? (fsl_cx *)cwal_native_get( nv, FSL_TYPEID(fsl_cx) )
        : NULL;
      if(f) return f;
      else v = cwal_value_prototype_get(e, v);
    } while(v);
    return NULL;
  }
}


/*
** Creates a new cwal_value (cwal_native) wrapper for the given fsl_db
** instance. If addDtor is true then a finalizer is installed for the
** instance, else it is assumed to live native-side and gets no
** destructor installed (in which case we will eventually have a problem
** when such a db is destroyed outside of the script API, unless we
** rewrite these to use weak references instead, but that might require
** one more level of struct indirection). The role of the db
** is important so that we can get the proper filename. A role of
** FSL_DBROLE_NONE should be used for non-fossil-core db handles.
**
** On success, returns 0 and assigns *rv to the new Db value.
**
** If 0!=db->filename.used then the new value gets a 'name' property
** set to the contents of db->filename.
**
** Maintenance reminder: this routine must return cwal_rc_t codes, not
** fsl_rc_e codes.
**
** TODO? Wrap up cwal_weak_ref of db handle, instead of the db handle
** itself? We only need this if Fossil instances will have their DB
** instances manipulated from outside of script-space.
*/
static int fsl_db_new_native( s2_engine * se, fsl_cx * f,
                              fsl_db * db, char addDtor,
                              cwal_value **rv,
                              fsl_dbrole_e role){
  cwal_native * n;
  cwal_value * nv;
  int rc = 0;
  char const * fname = NULL;
  fsl_size_t nameLen = 0;
  cwal_value * tmpV = NULL;
  assert(se && db && rv);
  n = cwal_new_native(se->e, db,
                      addDtor ? cwal_finalizer_f_fsl_db : NULL,
                      FSL_TYPEID(fsl_db));
  if(!n) return CWAL_RC_OOM;
  nv = cwal_native_value(n);
  cwal_value_ref(nv);
  /* Set up  "filename" property. It's problematic because
     of libfossil's internal DB juggling :/.
  */
  if(f) fname = fsl_cx_db_file_for_role(f, role, &nameLen);
  if(!fname){
    fname = fsl_db_filename(db, &nameLen);
  }
  if(fname){
    tmpV = cwal_new_string_value(se->e, fname, (cwal_size_t)nameLen);
    cwal_value_ref(tmpV);
    rc = tmpV
      ? cwal_prop_set(nv, "filename", 8, tmpV)
      : CWAL_RC_OOM;
    cwal_value_unref(tmpV);
    tmpV = 0;
    if(rc){
      goto end;
    }
    fname = 0;
  }

  /* Set up  "name" property. */
  if(f) fname = fsl_cx_db_name_for_role(f, role, &nameLen);
  if(!fname){
    fname = fsl_db_name(db);
    nameLen = fname ? cwal_strlen(fname) : 0;
  }
  if(fname){
    tmpV =
      cwal_new_string_value(se->e, fname, (cwal_size_t)nameLen);
    cwal_value_ref(tmpV);
    rc = tmpV
      ? cwal_prop_set(nv, "name", 4, tmpV)
      : CWAL_RC_OOM;
    cwal_value_unref(tmpV);
    tmpV = NULL;
    if(rc){
      goto end;
    }
  }
  end:
  if(!rc){
    *rv = nv;
    cwal_value_unhand(nv);
    cwal_value_prototype_set( nv, fsl_db_prototype(se) );
  }else{
    /*
      Achtung: if addDtor then on error the dtor will be called
      here.
    */
    cwal_value_unref(nv);
  }
  return rc;
}


/* TODO: fix corner case: inheritence via multiple levels of Native
   types will break this. */
#define THIS_DB s2_engine * se = s2_engine_from_args(args);             \
  cwal_native * nat = cwal_value_native_part(args->engine, args->self, FSL_TYPEID(fsl_db)); \
  cwal_value * natV = nat ? cwal_native_value(nat) : NULL;              \
  fsl_db * db = natV ? (fsl_db*)cwal_native_get( nat, FSL_TYPEID(fsl_db) ) : NULL; \
  assert(se);          \
  if(!se){/*potentially unused var*/} \
  if(!db){ return cb_toss(args, FSL_RC_TYPE, \
                          "'this' is not (or is no longer) "            \
                          "a Db instance."); } (void)0

#define THIS_STMT s2_engine * se = s2_engine_from_args(args);           \
  cwal_native * nat = cwal_value_native_part(args->engine, args->self,FSL_TYPEID(fsl_stmt)); \
  cwal_value * natV = nat ? cwal_native_value(nat) : NULL;              \
  fsl_stmt * stmt = natV ? (fsl_stmt*)cwal_native_get( nat, FSL_TYPEID(fsl_stmt) ) : NULL; \
  if(!se){/*potentially unused var*/} \
  assert(se);          \
  if(!stmt){ return cb_toss(args, FSL_RC_TYPE, \
                            "'this' is not (or is no longer) "          \
                            "a Stmt instance."); } (void)0

#define THIS_F s2_engine * se = s2_engine_from_args(args); \
  cwal_native * nat = cwal_value_native_part(args->engine, args->self,FSL_TYPEID(fsl_cx)); \
  cwal_value * natV = nat ? cwal_native_value(nat) : NULL;                   \
  fsl_cx * f = natV ? (fsl_cx*)cwal_native_get( nat, FSL_TYPEID(fsl_cx) ) : NULL; \
  if(!se){/*potentially unused var*/} \
  assert(se); \
  if(!f){ return cb_toss(args, FSL_RC_TYPE, \
                         "'this' is not (or is no longer) "         \
                         "a Fossil Context instance."); } (void)0


static int cb_fsl_db_finalize( cwal_callback_args const * args,
                               cwal_value **rv ){
  THIS_DB;
  cwal_native_clear( nat, 1 );
  return 0;
}

static int cb_fsl_stmt_finalize( cwal_callback_args const * args,
                                 cwal_value **rv ){
  THIS_STMT;
  cwal_native_clear( nat, 1 );
  return 0;
}


int cb_fsl_db_ctor( cwal_callback_args const * args,
                    cwal_value **rv ){
  cwal_value * v = NULL;
  int rc;
  s2_engine * se = s2_engine_from_args(args);
  char const * fn;
  char const * openMode;
  cwal_size_t fnLen = 0;
  int openFlags = 0;
  fsl_db * dbP = 0;
  fsl_cx * f = cwal_value_fsl_cx_part(args->engine, args->self)
    /* It's okay if this is NULL. It will only be set when we
       aFossilContext.dbOpen() is called.
    */;
  assert(se);
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], &fnLen)
    : NULL;
  if(!fn){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting a string (db file name) argument.");
  }
  openMode = (args->argc>1)
    ? cwal_value_get_cstr(args->argv[1], NULL)
    : NULL;
  if(!openMode){
    openFlags = FSL_OPEN_F_RWC;
  }else{
    openFlags = s2_fsl_openmode_to_flags( openMode, openFlags );
  }
  dbP = fsl_db_malloc();
  if(!dbP) return CWAL_RC_OOM;
  dbP->f = f;
  rc = fsl_db_open( dbP, fn, openFlags );
  if(rc){
    if(dbP->error.msg.used){
      rc = cb_toss(args, FSL_RC_ERROR,
                   "Db open failed: code #%d: %s",
                   dbP->error.code,
                   (char const *)dbP->error.msg.mem);
    }else{
      rc = cb_toss(args, FSL_RC_ERROR,
                   "Db open failed "
                   "with code #%d (%s).", rc,
                   fsl_rc_cstr(rc));
    }
    fsl_db_close(dbP);
  }
  else{
    rc = fsl_db_new_native(se, f, dbP, 1, &v, FSL_DBROLE_NONE);
    if(rc){
      fsl_db_close(dbP);
      dbP = NULL;
      assert(!v);
    }
  }
  if(rc){
    assert(!v);
  }else{
    assert(v);
    *rv  = v;
  }
  return rc;
}


/**
   Returns a new cwal_array value containing the result column names
   of the given statement . Returns NULL on error, else an array value.
*/
static cwal_value * s2_fsl_stmt_col_names( cwal_engine * e,
                                           fsl_stmt * st ){
  cwal_value * aryV = NULL;
  cwal_array * ary = NULL;
  char const * colName = NULL;
  int i = 0;
  int rc = 0;
  cwal_value * newVal = NULL;
  assert(st);
  if( ! st->colCount ) return NULL;
  ary = cwal_new_array(e);
  if( ! ary ) return NULL;
  rc = cwal_array_reserve(ary, (cwal_size_t)st->colCount);
  if(rc) return NULL;
  aryV = cwal_array_value(ary);
  assert(ary);
  for( i = 0; (0==rc) && (i < st->colCount); ++i ){
    colName = fsl_stmt_col_name(st, i);
    if( ! colName ) rc = CWAL_RC_OOM;
    else{
      newVal = cwal_new_string_value(e, colName,
                                     cwal_strlen(colName));
      if( NULL == newVal ){
        rc = CWAL_RC_OOM;
      }
      else{
        rc = cwal_array_set( ary, i, newVal );
        if( rc ) cwal_value_unref( newVal );
      }
    }
  }
  if( 0 == rc ) return aryV;
  else{
    cwal_value_unref(aryV);
    return NULL;
  }
}

static int s2_fsl_setup_new_stmt(s2_engine * se,
                                 cwal_value * nv,
                                 fsl_stmt * st){
  int rc;
  cwal_value * v;
  cwal_engine * e = se->e;
#define STMT_INCLUDE_SQL 0
#if STMT_INCLUDE_SQL
  fsl_size_t sqlLen = 0;
  char const * sql;
#endif
#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(K) VCHECK; rc = cwal_prop_set(nv, K, cwal_strlen(K), v); \
  if(rc) goto end;
#if STMT_INCLUDE_SQL
  /* Too costly, and so far never used. */
  sql = fsl_buffer_cstr2(&st->sql, &sqlLen);
  v = cwal_new_string_value(e, sql, (cwal_size_t)sqlLen)
    /* Reminder to self: we could get away with an X-string for 99.9%
       of use cases, but if someone holds a ref to that string
       after the statement is gone, blammo - cwal-level assertion
       at some point.
    */;
  SET("sql");
#endif
#undef STMT_INCLUDE_SQL
  v = cwal_new_integer(e, (cwal_int_t)st->colCount);
  SET("columnCount");
  v = cwal_new_integer(e, (cwal_int_t)st->paramCount);
  SET("parameterCount");
  if(st->colCount){
    v = s2_fsl_stmt_col_names(e, st);
    SET("columnNames");
  }
  cwal_value_prototype_set(nv, fsl_stmt_prototype(se));
#undef SET
#undef VCHECK
  assert(!rc);
  end:
  return rc;
}

static int cb_fsl_db_prepare( cwal_callback_args const * args,
                              cwal_value **rv ){
  fsl_stmt st = fsl_stmt_empty;
  int rc;
  char const * sql;
  cwal_size_t sqlLen = 0;
  cwal_value * nv = NULL;
  cwal_engine * e = args->engine;
  fsl_stmt * st2 = NULL;
  THIS_DB;
  sql = args->argc
    ? cwal_value_get_cstr(args->argv[0], &sqlLen)
    : NULL;
  if(!sql || !sqlLen){
    return cwal_exception_setf(e,
                               FSL_RC_MISUSE,
                               "Expecting a non-empty string "
                               "argument (SQL).");
  }
  assert(!st.stmt);
  rc = fsl_db_prepare( db, &st, "%.*s", (int)sqlLen, sql);
  if(rc){
    rc = cb_toss_db(args, db);
    assert(!st.stmt);
  }else{
    st2 = fsl_stmt_malloc();
    nv = st2
      ? cwal_new_native_value(e,
                              st2, cwal_finalizer_f_fsl_stmt,
                              FSL_TYPEID(fsl_stmt))
      : NULL;
    if(!nv){
      if(st2) fsl_free(st2);
      fsl_stmt_finalize(&st);
      st2 = NULL;
      rc = CWAL_RC_OOM;
    }else{
      void const * kludge = st2->allocStamp;
      *st2 = st;
      st2->allocStamp = kludge;
      rc = s2_fsl_setup_new_stmt(se, nv, st2);
      if(!rc) *rv = nv;
    }
  }
  if(rc && nv){
    cwal_value_unref(nv);
  }        
  return rc;
}

static int cb_fsl_db_filename( cwal_callback_args const * args,
                               cwal_value **rv ){
  char const * fname = NULL;
  fsl_size_t nameLen = 0;
  THIS_DB;
  fname = fsl_db_filename(db, &nameLen);
  *rv = fname
    ? cwal_new_string_value(args->engine, fname, nameLen)
    : cwal_value_null();
  return *rv ? 0 : CWAL_RC_OOM;
}

static int cb_fsl_db_name( cwal_callback_args const * args,
                               cwal_value **rv ){
  char const * fname = NULL;
  THIS_DB;
  fname = fsl_db_name(db);
  *rv = fname
    ? cwal_new_string_value(args->engine, fname, fsl_strlen(fname))
    : cwal_value_null();
  return *rv ? 0 : CWAL_RC_OOM;
}

/**
   Extracts result column ndx from st and returns a "the closest
   approximation" of its type in cwal_value form by assigning it to
   *rv. Returns 0 on success. On errror *rv is not modified.
   Does not trigger a th1ish/cwal exception on error.
*/
static int s2_fsl_stmt_to_value( cwal_engine * e,
                                 fsl_stmt * st,
                                 int ndx,
                                 cwal_value ** rv ){
  int vtype = sqlite3_column_type(st->stmt, ndx);
  switch( vtype ){
    case SQLITE_NULL:
      *rv = cwal_value_null();
      return 0;
    case SQLITE_INTEGER:
      *rv = cwal_new_integer( e,
                              (cwal_int_t)fsl_stmt_g_int64(st,ndx) );
      break;
    case SQLITE_FLOAT:
      *rv = cwal_new_double( e,
                             (cwal_double_t)fsl_stmt_g_double(st, ndx) );
      break;
    case SQLITE_BLOB: {
      int rc;
      fsl_size_t slen = 0;
      void const * bl = 0;
      rc = fsl_stmt_get_blob(st, ndx, &bl, &slen);
      if(rc){
        /* FIXME: we need a fsl-to-cwal rc converter (many of them line up). */
        return CWAL_RC_ERROR;
      }else if(!bl){
        *rv = cwal_value_null();
      }else{
        cwal_buffer * buf = cwal_new_buffer(e, (cwal_size_t)(slen+1));
        if(!buf) return CWAL_RC_OOM;
        rc = cwal_buffer_append( e, buf, bl, (cwal_size_t)slen );
        if(rc){
          assert(!*rv);
          cwal_value_unref(cwal_buffer_value(buf));
        }else{
          *rv = cwal_buffer_value(buf);
        }
      }
      break;
    }
    case SQLITE_TEXT: {
      fsl_size_t slen = 0;
      char const * str = 0;
      str = fsl_stmt_g_text( st, ndx, &slen );
      *rv = cwal_new_string_value(e, slen ? str : NULL,
                                  (cwal_size_t) slen);
      break;
    }
    default:
      return cwal_exception_setf(e, CWAL_RC_TYPE,
                                 "Unknown db column type (%d).",
                                 vtype);
  }
  return *rv ? 0 : CWAL_RC_OOM;
}

/**
   Extracts all columns from st into a new cwal_object value.
   colNames is expected to hold the same number of entries
   as st has columns, and in the same order. The entries in
   colNames are used as the object's field keys.

   Returns NULL on error.
*/
static cwal_value * s2_fsl_stmt_row_to_object2( cwal_engine * e,
                                                fsl_stmt * st,
                                                cwal_array const * colNames ){
  int colCount;
  cwal_value * objV;
  cwal_object * obj;
  cwal_string * colName;
  int i;
  int rc;
  if( ! st ) return NULL;
  colCount = st->colCount;
  if( !colCount || (colCount>(int)cwal_array_length_get(colNames)) ) {
    return NULL;
  }
  obj = cwal_new_object(e);
  if( ! obj ) return NULL;
  objV = cwal_object_value( obj );
  cwal_value_ref(objV);
  for( i = 0; i < colCount; ++i ){
    cwal_value * v = NULL;
    cwal_value * colNameV = cwal_array_get( colNames, i );
    colName = cwal_value_get_string( colNameV );
    if( ! colName ) goto error;
    rc = s2_fsl_stmt_to_value( e, st, i, &v );
    if( rc ) goto error;
    cwal_value_ref(v);
    rc = cwal_prop_set_v( objV, colNameV, v );
    cwal_value_unref(v);
    if( rc ){
      goto error;
    }
  }
  cwal_value_unhand(objV);
  return objV;
  error:
  cwal_value_unref( objV );
  return NULL;
}

/**
   Extracts all columns from st into a new cwal_object value,
   using st's column names as the keys.

   Returns NULL on error.
*/
static cwal_value * s2_fsl_stmt_row_to_object( cwal_engine * e,
                                               fsl_stmt * st ){
  cwal_value * objV;
  cwal_object * obj;
  char const * colName;
  int i;
  int rc;
  if( ! st || !st->colCount ) return NULL;
  obj = cwal_new_object(e);
  if( ! obj ) return NULL;
  objV = cwal_object_value(obj);
  cwal_value_ref(objV);
  for( i = 0; i < st->colCount; ++i ){
    cwal_value * v = NULL;
    colName = fsl_stmt_col_name(st, i);
    if( ! colName ) goto error;
    rc = s2_fsl_stmt_to_value( e, st, i, &v );
    if( rc ) goto error;
    cwal_value_ref(v);
    rc = cwal_prop_set( objV, colName, cwal_strlen(colName), v );
    cwal_value_unref(v);
    if( rc ){
      goto error;
    }
  }
  cwal_value_unhand(objV);
  return objV;
  error:
  cwal_value_unref( objV );
  return NULL;
}

/**
   Appends all result columns from st to ar.
*/
static int s2_fsl_stmt_row_to_array2( cwal_engine * e,
                                      fsl_stmt * st,
                                      cwal_array * ar)
{
  int i;
  int rc = 0;
  for( i = 0; i < st->colCount; ++i ){
    cwal_value * v = NULL;
    rc = s2_fsl_stmt_to_value( e, st, i, &v );
    if( rc ) goto end;
    cwal_value_ref(v);
    rc = cwal_array_append( ar, v );
    cwal_value_unref(v);
    if( rc ){
      goto end;
    }
  }
  end:
  return rc;
}

/**
   Converts all column values from st into a cwal_array
   value. Returns NULL on error, else an array value.
*/
static cwal_value * s2_fsl_stmt_row_to_array( cwal_engine * e,
                                              fsl_stmt * st )
{
  cwal_array * ar;
  int rc = 0;
  if( ! st || !st->colCount ) return NULL;
  ar = cwal_new_array(e);
  if( ! ar ) return NULL;
  rc = s2_fsl_stmt_row_to_array2( e, st, ar );
  if(rc){
    cwal_array_unref(ar);
    return NULL;
  }else{
    return cwal_array_value(ar);
  }
}

/**
   mode: 0==Object, >0==Array, <0==no row data (just boolean
   indicator)
*/
static int s2_fsl_stmt_step_impl( cwal_callback_args const * args,
                                  cwal_value **rv,
                                  int mode ){
  int rc;
  int scode;
  THIS_STMT;
  scode = fsl_stmt_step( stmt );
  switch(scode){
    case FSL_RC_STEP_DONE:
      rc = 0;
      *rv = cwal_value_undefined();
      break;
    case FSL_RC_STEP_ROW:{
      if(0==mode){
        /* Object mode */
        cwal_array const * colNames =
          cwal_value_get_array( cwal_prop_get(args->self,
                                              "columnNames", 11) );
        *rv = colNames
          ? s2_fsl_stmt_row_to_object2(args->engine,
                                       stmt, colNames)
          : s2_fsl_stmt_row_to_object(args->engine, stmt);
      }else if(mode>0){
        /* Array mode */
        *rv = s2_fsl_stmt_row_to_array(args->engine, stmt);
      }else{
        /* "Normal" mode */
        *rv = cwal_value_true();
      }
      rc = *rv ? 0 : CWAL_RC_OOM;
      break;
    }
    default:
      rc = cb_toss_db(args, stmt->db);
      break;
  }
  return rc;
}

static int cb_fsl_stmt_step_array( cwal_callback_args const * args, cwal_value **rv ){
  return s2_fsl_stmt_step_impl( args, rv, 1 );
}

static int cb_fsl_stmt_step_object( cwal_callback_args const * args, cwal_value **rv ){
  return s2_fsl_stmt_step_impl( args, rv, 0 );
}

static int cb_fsl_stmt_step( cwal_callback_args const * args, cwal_value **rv ){
  return s2_fsl_stmt_step_impl( args, rv, -1 );
}

static int cb_fsl_stmt_reset( cwal_callback_args const * args, cwal_value **rv ){
  char resetCounter = 0;
  int rc;
  THIS_STMT;
  if(1 < args->argc) resetCounter = cwal_value_get_bool(args->argv[1]);
  rc = fsl_stmt_reset2(stmt, resetCounter);
  if(!rc) *rv = args->self;
  return rc
    ? cb_toss_db( args, stmt->db )
    : 0;
}

static int cb_fsl_stmt_row_to_array( cwal_callback_args const * args,
                                     cwal_value **rv ){
  int rc;
  THIS_STMT;
  if(!stmt->rowCount) return cb_toss(args, FSL_RC_MISUSE,
                                     "Cannot fetch row data from an "
                                     "unstepped statement.");
  *rv = s2_fsl_stmt_row_to_array(args->engine, stmt);
  if(*rv) rc = 0;
  else rc = cb_toss(args, CWAL_RC_ERROR,
                    "Unknown error converting statement "
                    "row to Array.");
  return rc;
}

static int cb_fsl_stmt_row_to_object( cwal_callback_args const * args,
                                      cwal_value **rv ){
  int rc;
  THIS_STMT;
  if(!stmt->rowCount) return cb_toss(args, FSL_RC_MISUSE,
                                     "Cannot fetch row data from "
                                     "an unstepped statement.");
  *rv = s2_fsl_stmt_row_to_object(args->engine, stmt);
  if(*rv) rc = 0;
  else rc = cb_toss(args, CWAL_RC_ERROR,
                    "Unknown error converting statement row "
                    "to Object.");
  return rc;
}


/**
   Tries to bind v to the given parameter column (1-based) of
   nv->stmt.  Returns 0 on success, triggers a script-side exception
   on error.
*/
static int s2_fsl_stmt_bind( cwal_engine * e,
                             fsl_stmt * st,
                             int ndx,
                             cwal_value * v ){
  int rc;
  int const vtype = v ? cwal_value_type_id(v) : CWAL_TYPE_NULL;
  if(ndx<1) {
    return cwal_exception_setf(e, FSL_RC_RANGE,
                               "Bind index %d is invalid: indexes are 1-based.",
                               ndx);
  }
  else if(ndx > st->paramCount) {
    return cwal_exception_setf(e, FSL_RC_RANGE,
                               "Bind index %d is out of range. Range=(1..%d).",
                               ndx, st->paramCount);
  }
  /*MARKER(("Binding %s to column #%u\n", cwal_value_type_name(v), ndx));*/
  switch( vtype ){
    case CWAL_TYPE_NULL:
    case CWAL_TYPE_UNDEF:
      rc = fsl_stmt_bind_null(st, ndx);
      break;
    case CWAL_TYPE_BOOL:
      rc = fsl_stmt_bind_int32(st, ndx, cwal_value_get_bool(v));
      break;
    case CWAL_TYPE_INTEGER:
      /* We have no way of knowing which type (32/64-bit) to bind
         here, so we'll guess. We could check the range, i guess,
         but for sqlite it makes little or no difference, anyway.
      */
      rc = fsl_stmt_bind_int64(st, ndx,
                               (int64_t)cwal_value_get_integer(v));
      break;
    case CWAL_TYPE_DOUBLE:
      rc = fsl_stmt_bind_double(st, ndx, (double)cwal_value_get_double(v));
      break;
    case CWAL_TYPE_BUFFER:
    case CWAL_TYPE_STRING: {
      /* FIXME: bind using the fsl_stmt_bind_xxx() APIs!!!
         string/buffer binding is currently missing.
      */
      cwal_size_t slen = 0;
      char const * cstr = cwal_value_get_cstr(v, &slen);
      if(!cstr){
        /* Will only apply to empty buffers (Strings are never
           NULL). But it's also possible that a buffer with
           length 0 has a non-NULL memory buffer. So we cannot,
           without further type inspection, clearly
           differentiate between a NULL and empty BLOB
           here. This distinction would seem to be (?) 
           unimportant for this particular use case, so
           fixing/improving it can wait.
        */
        rc = fsl_stmt_bind_null(st, ndx);
      }
#if 1
      else if(CWAL_TYPE_BUFFER==vtype){
        rc = fsl_stmt_bind_blob(st, ndx, cstr, (int)slen, 1);
      }
#endif
      else{
        rc = fsl_stmt_bind_text(st, ndx, cstr, (int)slen, 1);
      }
      break;
    }
    default:
      return cwal_exception_setf(e, FSL_RC_TYPE,
                                 "Unhandled data type (%s) for binding "
                                 "column %d.",
                                 cwal_value_type_name(v), ndx);
  }
  if(rc){
    fsl_size_t msgLen = 0;
    char const * msg = NULL;
    int const dbRc = fsl_db_err_get(st->db, &msg, &msgLen);
    rc = cwal_exception_setf(e, FSL_RC_DB,
                             "Binding column %d failed with "
                             "sqlite code #%d: %.*s", ndx,
                             dbRc, (int)msgLen, msg);
  }
  return rc;
}

static int s2_fsl_stmt_bind_values_a( cwal_engine * e,
                                      fsl_stmt * st,
                                      cwal_array const * src){
  int const n = (int)cwal_array_length_get(src);
  int i = 0;
  int rc = 0;
  for( ; !rc && (i < n); ++i ){
    rc = s2_fsl_stmt_bind( e, st, i+1,
                           cwal_array_get(src,(cwal_size_t)i) );
  }
  return rc;
}

/**
   Internal helper for binding by name.
*/
typedef struct {
  fsl_stmt * st;
  cwal_engine * e;
} BindByNameState;

/**
   Property visitor helper for binding parameters by name.
*/
static int cwal_kvp_visitor_f_bind_by_name( cwal_kvp const * kvp, void * state ){
  cwal_size_t keyLen = 0;
  cwal_value const * v = cwal_kvp_key(kvp);
  char const * key = cwal_value_get_cstr(v, &keyLen);
  BindByNameState const * bbn = (BindByNameState const *)state;
  int ndx;
  if(!key){
    if(!cwal_value_is_integer(v)){
      return 0 /* non-string property. */;
    }else{
      ndx = (int)cwal_value_get_integer(v);
    }
  }else{
    ndx = fsl_stmt_param_index(bbn->st, key);
    if(ndx<=0){
      return cwal_exception_setf(bbn->e, CWAL_RC_RANGE, "Parameter name '%.*s' "
                                 "does not resolve to an index. (Maybe missing "
                                 "the leading ':' or '$' part of the param name?)",
                                 (int)keyLen, key);
    }
  }
  return s2_fsl_stmt_bind(bbn->e, bbn->st, ndx, cwal_kvp_value(kvp));
}

/**
   Internal helper for binding values to statements.

   bind may be either an Array of values to bind, a container of param
   names (incl. ':' or '$' prefix), or a single value, to bind at the
   given index. For arrays/containers, the given index is ignored. As
   a special case, if bind === cwal_value_undefined() then it is
   simply ignored.
*/
static int s2_fsl_stmt_bind_proxy( cwal_engine * e,
                                   fsl_stmt * st,
                                   int ndx,
                                   cwal_value * bind){
  int rc = 0;
  if(cwal_value_undefined() == bind) rc = 0;
  else if(cwal_value_is_array(bind)){
    rc = s2_fsl_stmt_bind_values_a(e, st,
                                   cwal_value_get_array(bind));
  }else if(cwal_props_can(bind)){
    BindByNameState bbn;
    bbn.e = e;
    bbn.st = st;
    rc = cwal_props_visit_kvp(bind, cwal_kvp_visitor_f_bind_by_name,
                              &bbn);
  }else{
    rc = s2_fsl_stmt_bind( e, st, ndx, bind);
  }
  return rc;
}

#if 0
static int s2_fsl_stmt_bind_from_args( cwal_engine * e,
                                       fsl_stmt *st,
                                       cwal_callback_args const * args,
                                       uint16_t startAtArg ){
  int rc = 0;
  cwal_value * bind = (args->argc < startAtArg)
    ? args->argv[startAtArg]
    : NULL;
  if(!bind) return 0;
  if(cwal_value_is_array(bind)){
    rc = s2_fsl_stmt_bind_values_a(e, st,
                                   cwal_value_get_array(bind));
  }else if(cwal_value_is_object(bind)){
    MARKER(("TODO: binding by name"));
    rc = CWAL_RC_ERROR;
  }else if(!cwal_value_is_undef(bind) && (args->argc > startAtArg)){
    int i = startAtArg;
    for( ; !rc && (i < (int)args->argc); ++i ){
      rc = s2_fsl_stmt_bind( e, st, i, args->argv[i]);
    }
  }
  return rc;
}
#endif

static int cb_fsl_stmt_bind( cwal_callback_args const * args,
                             cwal_value **rv ){
  cwal_int_t ndx = -999;
  int rc;
  THIS_STMT;
  if(!args->argc){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting (integer Index [,Value=null]) or "
                   "(Array|Object|undefined) arguments.");
  }
  if(cwal_value_undefined() == args->argv[0]){
    /* Special case to simplify some script code */
    *rv = args->self;
    return 0;
  }else if(cwal_value_is_integer(args->argv[0])){
    ndx = cwal_value_get_integer(args->argv[0]);
    if(1>ndx){
      return cb_toss(args, FSL_RC_RANGE,
                     "SQL bind() indexes are 1-based.");
    }
  }
  rc = s2_fsl_stmt_bind_proxy(args->engine, stmt,
                              (int)(-999==ndx ? 1 : ndx),
                              (-999==ndx)
                              ? args->argv[0]
                              : ((args->argc>1)
                                 ? args->argv[1]
                                 : NULL));
  if(!rc) *rv = args->self;
  return rc;
}

static int cb_fsl_stmt_get( cwal_callback_args const * args, cwal_value **rv ){
  int rc;
  cwal_int_t ndx;
  THIS_STMT;
  if(!args->argc || !cwal_value_is_integer(args->argv[0])){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting Index arguments.");
  }
  else if(!stmt->colCount){
    return cb_toss(args, FSL_RC_MISUSE,
                   "This is not a fetch-style statement.");
  }
  ndx = cwal_value_get_integer(args->argv[0]);
  if((ndx<0) || (ndx>=stmt->colCount)){
    return cb_toss(args, CWAL_RC_RANGE,
                   "Column index %d is out of range. "
                   "Valid range is (%d..%d).", ndx,
                   0, stmt->colCount-1);
  }
  rc = s2_fsl_stmt_to_value( args->engine, stmt,
                             (uint16_t)ndx, rv );
  if(rc && (CWAL_RC_EXCEPTION!=rc) && (CWAL_RC_OOM!=rc)){
    rc = cb_toss( args, rc, "Get-by-index failed with code %d (%s).",
                  rc, cwal_rc_cstr(rc));
  }
  return rc;
}

static int cb_fsl_db_exec_impl( cwal_callback_args const * args,
                                cwal_value **rv,
                                char isMulti ){
  int rc = 0;
  int argIndex = 0;
  THIS_DB;
  do{ /* loop on the arguments, expecting SQL for each one... */
    cwal_size_t sqlLen = 0;
    char const * sql = (args->argc>argIndex)
      ? cwal_value_get_cstr(args->argv[argIndex], &sqlLen)
      : NULL;
    if(!sql || !sqlLen){
      rc = (argIndex>0)
        ? 0
        : cb_toss(args,
                  FSL_RC_MISUSE,
                  "Expecting a non-empty string/buffer "
                  "argument (SQL).");
      break;
    }
    /* MARKER(("SQL:<<<%.*s>>>\n", (int)sqlLen, sql)); */
    rc = isMulti
      ? fsl_db_exec_multi( db, "%.*s", (int)sqlLen, sql )
      : fsl_db_exec( db, "%.*s", (int)sqlLen, sql )
      ;
    if(rc) rc = cb_toss_db( args, db );
  }while(!rc && (++argIndex < args->argc));
  if(!rc) *rv = args->self;
  return rc;
}

static int cb_fsl_db_exec_multi( cwal_callback_args const * args,
                                 cwal_value **rv ){
  return cb_fsl_db_exec_impl( args, rv, 1 );
}

static int cb_fsl_db_exec( cwal_callback_args const * args,
                           cwal_value **rv ){
  return cb_fsl_db_exec_impl( args, rv, 0 );
}

static int cb_fsl_db_last_insert_id( cwal_callback_args const * args,
                                     cwal_value **rv ){
  THIS_DB;
  *rv = cwal_new_integer(args->engine,
                         (cwal_int_t)fsl_db_last_insert_id(db));
  return *rv ? 0 : CWAL_RC_OOM;
}

/**
   If !cb, this is a no-op, else if cb is-a Function, it is call()ed,
   if it is a String/Buffer, it is eval'd, else an exception is
   thrown.  self is the 'this' for the call(). Result is placed in
   *rv.  Returns 0 on success.
   */
static int s2_fsl_stmt_each_call_proxy( s2_engine * se, fsl_stmt * st,
                                        cwal_value * cb, cwal_value * self,
                                        cwal_value **rv ){
  char const * cstr;
  cwal_size_t strLen = 0;
  char const useNewScope = 1
    /*
      We need a new scope to avoid that evaluation of a string callback
      vacuums up self.
    */;
  if(!cb) return 0;
  else if((cstr = cwal_value_get_cstr(cb,&strLen))){
    /** TODO: tokenize this only the first time. */
    s2_ptoker pr = s2_ptoker_empty;
    cwal_scope s2Scope = cwal_scope_empty;
    int rc;
    /**
       Bug Reminder: in stack traces and assertion traces the
       script name/location info is wrong in this case (empty and
       relative to cstr, respectively) because s2 doesn't have
       a way to map this string back to a source location at this
       point.
    */
    if(useNewScope){
      rc = cwal_scope_push2(se->e, &s2Scope);
      if(rc) return rc;
    }
    s2_ptoker_init(&pr, cstr, (int)strLen);
    pr.name = "Db.each() callback";
    rc = s2_eval_ptoker(se, &pr, 0, rv);
    if(CWAL_RC_RETURN==rc){
      rc = 0;
      *rv = cwal_propagating_take(se->e);
    }
    if(useNewScope){
      assert(se->e->current == &s2Scope);
      cwal_scope_pop2(se->e, rc ? 0 : *rv);
    }
    return rc;
  }else if(cwal_value_is_function(cb)){
    cwal_function * f = cwal_value_get_function(cb);
    if(useNewScope){
      return cwal_function_call(f, self, rv, 0, NULL );
    }else{
      cwal_scope * sc = cwal_scope_current_get(se->e);
      return cwal_function_call_in_scope(sc, f, self, rv,
                                         0, NULL );
    }
  }else {
    return cwal_exception_setf(se->e, FSL_RC_MISUSE,
                               "Don't now how to handle callback "
                               "of type '%s'.",
                               cwal_value_type_name(cb));
  }
}

/**
   Script usage:

   db.each({
   sql: "SQL CODE", // required
   bind: X, // parameter value or array of values to bind.
            // Passing the undefined value is the same as
            // not passing any value.
   mode: 0, // 0==rows as objects, else as arrays (default)
   callback: string | function // called for each row
   })

   Only the 'sql' property is required, and 'bind' must be set
   if the SQL contains any binding placeholders.

   In the scope of the callback, 'this' will resolve to the current
   row data, either in object or array form (depending on the 'mode'
   property). If the callback throws, that exception is propagated.
   If it returns a literal false (as opposed to another falsy value)
   then iteration stops without an error.

   In addition, the following scope-level variables are set:

   - rowNumber: 1-based number of the row (the iteration count).

   - columnNames: array of column names for the result set.
  
   Example callbacks:

   <<<EOF
     print(rowNumber, columnNames, this);
     print(this.0 + this.1); // array-mode column access
   EOF

   proc(){
     print(rowNumber, columnNames, this);
     print(this.colA + this.colB); // object-mode column access
   }

   Using the string form should be ever so slightly more efficient.
*/
static int cb_fsl_db_each( cwal_callback_args const * args, cwal_value **rv ){
  int rc = 0;
  cwal_value const * sql;
  char const * csql;
  cwal_size_t sqlLen = 0;
  fsl_stmt st = fsl_stmt_empty;
  int scode;
  cwal_value * vMode;
  int mode;
  cwal_value * props;
  cwal_value * bind;
  cwal_value * callback;
  cwal_value * cbSelf = NULL;
  cwal_array * cbArray = NULL;
  cwal_value * colNamesV = 0;
  cwal_array * colNames = 0;
  cwal_int_t rowNum;
  cwal_engine * e = args->engine;
  THIS_DB;
  if(!args->argc || !cwal_value_is_object(args->argv[0])){
    return cwal_exception_setf(e, FSL_RC_MISUSE,
                               "Expecting Object parameter.");
  }
  props = args->argv[0];
  sql = cwal_prop_get(props, "sql", 3 );
  csql = sql ? cwal_value_get_cstr(sql, &sqlLen) : NULL;
  if(!csql){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Missing 'sql' string/buffer property.");
  }
  vMode = cwal_prop_get( props, "mode", 4 );
  mode = vMode ? cwal_value_get_integer(vMode) : 1;
  bind = cwal_prop_get( props, "bind", 4 );
  callback = cwal_prop_get( props, "callback", 8 );
  if(callback){
    if(!cwal_value_is_function(callback)
       && !cwal_value_get_cstr(callback, 0)){
      callback = NULL;
    }
  }
  rc = fsl_db_prepare(db, &st, "%.*s", (int)sqlLen, csql);
  if(rc){
    return cb_toss_db(args, db);
  }
  if(bind && !cwal_value_is_undef(bind)){
    rc = s2_fsl_stmt_bind_proxy( e, &st, 1, bind );
    if(rc) goto end;
  }
  if(st.colCount){
    colNamesV = s2_fsl_stmt_col_names(e,&st);
    if(!colNamesV){
      rc = CWAL_RC_OOM;
      goto end;
    }
    cwal_value_ref(colNamesV);
    rc = s2_var_set( se, 0, "columnNames", 11, colNamesV );
    cwal_value_unref(colNamesV);
    if(rc){
      colNamesV = NULL;
      goto end;
    }
    colNames = cwal_value_get_array(colNamesV);
    assert(cwal_array_length_get(colNames) == (cwal_size_t)st.colCount);
  }
  rc = s2_var_set( se, 0, "columnCount", 11,
                   cwal_new_integer(e, (cwal_int_t)st.colCount) );
  if(rc) goto end;
    
  /*
    Step through each row and call the given callback function/string...
  */
  for( rowNum = 1;
       FSL_RC_STEP_ROW == (scode = fsl_stmt_step( &st ));
       ++rowNum){
    if(callback && !cbSelf){
      /** Init callback info if needed */
      cbArray = (0==mode)
        ? NULL
        : cwal_new_array(e);
      cbSelf = cbArray
        ? cwal_array_value(cbArray)
        : cwal_new_object_value(e);
      if(!cbSelf){
        rc = CWAL_RC_OOM;
        break;
      }else{
        cwal_value_ref(cbSelf);
        rc = s2_var_set(se, 0, "this", 4, cbSelf);
        cwal_value_unref(cbSelf);
        if(rc){
          goto end;
        }
      }
    }
    if(cbSelf){
      /**
         Set up this.XXX to each column's value, where
         XXX is either the column index (for array mode)
         or column name (for object mode).
      */
      cwal_value * frv = 0;
      int i = 0;
      for( ; !rc && (i < st.colCount); ++i ){
        cwal_value * cv;
        cv = NULL;
        rc = s2_fsl_stmt_to_value(e, &st, i, &cv);
        if(rc && (CWAL_RC_EXCEPTION!=rc) && (CWAL_RC_OOM!=rc)){
          rc = cwal_exception_setf(e, rc,
                                   "Conversion from db column to "
                                   "value failed with code "
                                   "%d (%s).",
                                   rc, fsl_rc_cstr(rc));
        }
        cwal_value_ref(cv);
        if(cbArray) rc = cwal_array_set( cbArray, i, cv);
        else{
          cwal_value * key;
          assert(colNames);
          key = cwal_array_get(colNames, i);
          assert(key);
          rc = cwal_prop_set_v( cbSelf, key, cv);
        }
        cwal_value_unref(cv);
      }
      if(rc) goto end;
      rc = s2_var_set( se, 0, "rowNumber", 9,
                       cwal_new_integer(e, rowNum) );
      if(rc) goto end /* leave potentially leaked new integer for the
                         scope to clean up */;
      rc = s2_fsl_stmt_each_call_proxy(se, &st, callback, cbSelf, &frv);
      if(rc) goto end;
      else if(frv == cwal_value_false()/*yes, a ptr comparison*/){
        /* If the function returns literal false, stop
           looping without an error. */
        goto end;
      }
      cbSelf = 0 /* Causes the next iteration to create a new
                  * array/object per row, overwriting "this". */;
    }
  }
  if(FSL_RC_STEP_ERROR==scode){
    rc = cwal_exception_setf(e, FSL_RC_DB,
                             "Stepping cursor failed.");
  }
  end:
  if(st.stmt){
    fsl_stmt_finalize( &st );
  }
  if(!rc) *rv = args->self;
  return rc;
}

/**
   Value DB.selectValue(string SQL [, bind = undefined [,defaultResult=undefined]])
*/
static int cb_fsl_db_select_value( cwal_callback_args const * args, cwal_value **rv ){
  int rc = 0, dbrc = 0;
  char const * sql;
  cwal_size_t sqlLen = 0;
  fsl_stmt st = fsl_stmt_empty;
  cwal_value * bind = 0;
  cwal_value * dflt = 0;
  THIS_DB;
  sql = args->argc ? cwal_value_get_cstr(args->argv[0], &sqlLen) : 0;
  if(!sql || !sqlLen){
    return cwal_exception_setf(args->engine, FSL_RC_MISUSE,
                               "Expecting SQL string/buffer parameter.");
  }
  bind = args->argc>1 ? args->argv[1] : 0;

  dbrc = fsl_db_prepare( db, &st, "%.*s", (int)sqlLen, sql );
  if(dbrc){
    rc = cb_toss_db(args, db);
    assert(rc);
    goto end;
  }

  if(bind){
    if(2==args->argc && !st.paramCount){
      dflt = bind;
    }else{
      rc = s2_fsl_stmt_bind_proxy(args->engine, &st, 1, bind);
    }
    bind = 0;
  }

  if( !rc && (FSL_RC_STEP_ROW == (dbrc=fsl_stmt_step(&st)))){
    cwal_value * xrv = 0;
    rc = s2_fsl_stmt_to_value(args->engine, &st, 0, &xrv);
    if(!rc){
      *rv = xrv ? xrv : cwal_value_undefined();
    }
  }else if(FSL_RC_STEP_DONE == dbrc){
    *rv = dflt ? dflt : cwal_value_undefined();
  }else if(FSL_RC_STEP_ERROR == dbrc){
    assert(st.db->error.code);
    rc = cb_toss_db(args, st.db);
  }
  end:
  fsl_stmt_finalize(&st);
  return rc;  
}


/**
   Array Db.selectValues(string|buffer SQL [, bind = undefined])
*/
static int cb_fsl_db_select_values( cwal_callback_args const * args, cwal_value **rv ){
  int rc = 0, dbrc = 0;
  char const * sql;
  cwal_size_t sqlLen = 0;
  fsl_stmt st = fsl_stmt_empty;
  cwal_value * bind = 0;
  cwal_array * ar = 0;
  cwal_value * arV = 0;
  THIS_DB;
  sql = args->argc ? cwal_value_get_cstr(args->argv[0], &sqlLen) : 0;
  if(!sql || !sqlLen){
    return cwal_exception_setf(args->engine, FSL_RC_MISUSE,
                               "Expecting SQL string/buffer parameter.");
  }
  bind = args->argc>1 ? args->argv[1] : 0;

  dbrc = fsl_db_prepare( db, &st, "%.*s", (int)sqlLen, sql );
  if(dbrc){
    rc = cb_toss_db(args, db);
    assert(rc);
    goto end;
  }

  ar = cwal_new_array(args->engine);
  if(!ar){
    rc = CWAL_RC_OOM;
    goto end;
  }
  arV = cwal_array_value(ar);
  cwal_value_ref(arV);

  if(bind){
    rc = s2_fsl_stmt_bind_proxy(args->engine, &st, 1, bind);
    bind = 0;
  }

  while( !rc && (FSL_RC_STEP_ROW == (dbrc=fsl_stmt_step(&st)))){
    cwal_value * col = 0;
    rc = s2_fsl_stmt_to_value(args->engine, &st, 0, &col);
    if(!rc){
      cwal_value_ref(col);
      rc = cwal_array_append(ar, col);
      cwal_value_unref(col);
    }
  }

  end:
  fsl_stmt_finalize(&st);
  if(rc){
    cwal_value_unref(arV);
  }else if(arV){
    *rv = arV;
    cwal_value_unhand(arV);
  }
  return rc;  
}

static int cb_fsl_db_trans_begin( cwal_callback_args const * args, cwal_value **rv ){
  int rc;
  THIS_DB;
  rc = fsl_db_transaction_begin(db);
  if(rc){
    rc = cb_toss_db(args, db);
  }
  if(!rc) *rv = args->self;
  return rc;
}

static int cb_fsl_db_trans_end( cwal_callback_args const * args, cwal_value **rv, int mode ){
  int rc;
  THIS_DB;
  if(db->beginCount<=0){
    return cb_toss( args, CWAL_RC_RANGE, "No transaction is active.");
  }
  if(mode < 0){
    rc = fsl_db_rollback_force(db);
  }else{
    rc = fsl_db_transaction_end(db, mode ? 1 : 0);
  }
  if(rc){
    if(db->error.code){
      rc = cb_toss_db(args, db);
    }else{
      rc = cb_toss( args, CWAL_RC_ERROR,
                    "fsl error code during %s: %d (%s)",
                    (mode<0
                     ? "forced rollback"
                     : (mode>0
                        ? "rollback"
                        : "db commit" /* as opposed to repo checkin/commit */)
                     ),
                    rc, fsl_rc_cstr(rc) );
    }
  }else{
    *rv = args->self;
  }
  return rc;
}

static int cb_fsl_db_trans_commit( cwal_callback_args const * args, cwal_value **rv ){
  return cb_fsl_db_trans_end( args, rv, 0 );
}

static int cb_fsl_db_trans_rollback( cwal_callback_args const * args, cwal_value **rv ){
  return cb_fsl_db_trans_end( args, rv,
                              (args->argc
                               && cwal_value_get_bool(args->argv[0]))
                              ? -1 /* force immediate rollback */
                              : 1 );
}

static int cb_fsl_db_trans_state( cwal_callback_args const * args, cwal_value **rv ){
  int bc;
  THIS_DB;
  bc = db->beginCount > 0 ? db->beginCount : 0;
  *rv = cwal_new_integer(args->engine,
                         (cwal_int_t)(db->doRollback ? -bc : bc));
  return *rv ? 0 : CWAL_RC_OOM;
}

static int cb_fsl_db_trans( cwal_callback_args const * args, cwal_value **rv ){
  int rc;
  cwal_function * func;
  THIS_DB;
  func = args->argc ? cwal_value_function_part(args->engine, args->argv[0]) : 0;
  if(!func){
    return cb_toss(args, CWAL_RC_MISUSE, "Expecting a Function argument.");
  }
  rc = fsl_db_transaction_begin( db );
  if(rc){
    return db->error.code
      ? cb_toss_db(args, db)
      : cb_toss(args, CWAL_RC_ERROR,
                "Error #%d (%s) while starting transaction.",
                rc, fsl_rc_cstr(rc));
  }
  rc = cwal_function_call( func, args->self, 0, 0, 0 );
  if(rc){
    fsl_db_transaction_end( db, 1 );
#if 0
    /* Just proving to myself that this rollback is still called in
       the face of an s2-level exit()/fatal()/assert(). It does. We
       can in fact preempt a FATAL result here, but doing so is a bad
       idea.
    */
    if(CWAL_RC_FATAL==rc){
      cwal_exception_set(args->engine, cwal_propagating_take(args->engine));
      rc = CWAL_RC_EXCEPTION;
    }
#endif
  }else{
    rc = fsl_db_transaction_end( db, 0 );
    if(rc){
      rc = db->error.code
        ? cb_toss_db(args, db)
        : cb_toss(args, CWAL_RC_ERROR,
                  "Error #%d (%s) while committing transaction.",
                  rc, fsl_rc_cstr(rc));
    }else{
      *rv = args->self;
    }
  }
  return rc;
}


/**
 ** If cx has a property named "db", it is returned, else if
 ** createIfNotExists is true then a new native fsl_db Object named
 ** "db" is inserted into cx and returned. Returns NULL on allocation
 ** error or if no such property exists and createIfNotExists is
 ** false.
 */
static cwal_value * fsl_cx_db_prop( fsl_cx * f,
                                    cwal_value * fv,
                                    s2_engine * se,
                                    char createIfNotExists){
  cwal_value * rv;
  fsl_db * db = fsl_cx_db(f);
  /* assert(db); */
  rv = db ? cwal_prop_get(fv, "db", 2) : NULL;
  if(db && !rv && createIfNotExists){
    int rc = fsl_db_new_native(se, f, db, 0, &rv, FSL_DBROLE_MAIN);
    if(rc) return NULL;
    cwal_value_ref(rv);
    if(rv){
      rc = cwal_prop_set(fv, "db", 2, rv);
      cwal_value_unref(rv);
      if(rc) rv = NULL;
    }
  }
  return rv;
}


/**
 ** Internal helper which adds vSelf->db->repo/checkout/config
 ** handles. Returns 0 on success. MUST return CWAL_RC_xxx codes, NOT
 ** FSL_RC_xxx codes.
 */
static int fsl_cx_add_db_handles(fsl_cx * f, cwal_value * vSelf,
                                 s2_engine * se){
  cwal_value * dbV = fsl_cx_db_prop(f, vSelf, se, 1);
  cwal_value * d = NULL;
  fsl_db * db;
  int rc;
  cwal_size_t nameLen;
  if(!dbV) return FSL_RC_OOM;
#define DBH(NAME, GETTER, ROLE)                 \
  nameLen = cwal_strlen(NAME); \
  d = cwal_prop_get(dbV, NAME, nameLen); \
  if(!d && (db = GETTER(f))){ \
    rc = fsl_db_new_native(se, f, db, 0, &d, ROLE); \
    if(rc) return rc; \
    cwal_value_ref(d); \
    assert(d); \
    rc = cwal_prop_set(dbV, NAME, nameLen, d); \
    cwal_value_unref(d); \
    if(rc) return rc; \
  }
  /*DBH("main", fsl_cx_db, FSL_DBROLE_MAIN);*/
  DBH("repo", fsl_cx_db_repo, FSL_DBROLE_REPO);
  DBH("checkout", fsl_cx_db_ckout, FSL_DBROLE_CKOUT);
  DBH("config", fsl_cx_db_config, FSL_DBROLE_CONFIG);
#undef DBH
  return 0;    
}

static int cb_fsl_config_open( cwal_callback_args const * args,
                               cwal_value **rv ){
  char const * dbName;
  int rc;
  THIS_F;
  dbName = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if(args->argc && (!dbName || !*dbName)){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting a non-empty string argument.");
  }
  rc = fsl_config_open( f, (dbName && *dbName) ? dbName : 0 );
  if(rc){
    rc = cb_toss_fsl(args, f);
  }else{
    rc = fsl_cx_add_db_handles(f, args->self, se);
    if(!rc) *rv = args->self;
  }
  return rc;
}

#if 0
/* this gets trickier than i'd like because of the script-side
   db handles.
*/
static int cb_fsl_config_close( cwal_callback_args const * args,
                                cwal_value **rv ){
  THIS_F;
  fsl_config_close(f);
  *rv = args->self;
  return 0;
}
#endif

static int cb_fsl_repo_open( cwal_callback_args const * args,
                             cwal_value **rv ){
  char const * dbName;
  int rc;
  THIS_F;
  dbName = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if(!dbName || !*dbName){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting a non-empty string argument.");
  }
  rc = fsl_repo_open( f, dbName );
  if(rc){
    rc = cb_toss_fsl(args, f);
  }else{
    rc = fsl_cx_add_db_handles(f, args->self, se);
    if(!rc) *rv = args->self;
  }
  return rc;
}

static int cb_fsl_ckout_open_dir( cwal_callback_args const * args,
                                  cwal_value **rv ){
  char const * dbName = 0;
  cwal_size_t nameLen = 0;
  int rc;
  int checkParentDirs;
  THIS_F;
  dbName = args->argc
    ? cwal_value_get_cstr(args->argv[0], &nameLen)
    : 0;
  checkParentDirs = args->argc>1
    ? (cwal_value_get_bool(args->argv[1]) ? 1 : 0)
    : 1;
  rc = fsl_ckout_open_dir( f, dbName, checkParentDirs );
  if(rc){
    rc = cb_toss_fsl(args, f);
  }else{
    rc = fsl_cx_add_db_handles(f, args->self, se);
    if(!rc) *rv = args->self;
  }
  return rc;
}

static int cb_fsl_login_cookie_name( cwal_callback_args const * args,
                                     cwal_value ** rv){
  int rc;
  char * name;
  THIS_F;
  name = fsl_repo_login_cookie_name(f);
  if(name){
    assert( name[22] );
    assert( !name[23] );
    *rv = cwal_string_value(cwal_new_stringf(args->engine, "%.23s", name))
      /* z-string is only legal if libfossil and s2 use the same
         allocator, so we'll be pedantically correct here and
         copy it. String interning might make this a no-op.
      */;
    rc = *rv ? 0 : CWAL_RC_OOM;
    fsl_free(name);
  }else{
    *rv = cwal_value_undefined();
    rc = 0;
    /*cb_toss(args, CWAL_RC_ERROR,
      "Unexpected FossilContext state: "
      "no login cookie name.");*/
  }
  return rc;
}

/*
** Constructor function for fsl_cx instances.
**
** Script-side usage:
**
** var f = ThisFunc(object{...})
**
**
** The parameter object is optional. The only supported option at the
** moment is the traceSql boolean, which enables or disabled SQL
** tracing output.
**
** On success it returns (via *rv) a new cwal_native which is bound to
** a new fsl_cx instance. The fsl_cx will be initialized such that
** fsl_output() and friends will be redirected through cwal_output(),
** to allow fsl_output()-generated output to take advantage of
** th1ish's output buffering and whatnot.
*/
static int cb_fsl_cx_ctor( cwal_callback_args const * args,
                           cwal_value **rv ){
  fsl_cx * f = NULL;
  cwal_value * v;
  int rc;
  fsl_cx_init_opt init = fsl_cx_init_opt_empty;
  s2_engine * se = s2_engine_from_args(args);
  assert(se);

  init.output.out = fsl_output_f_cwal_output;
  init.output.flush = fsl_flush_f_cwal_out;
  init.output.state.state = args->engine;
  if(args->argc && cwal_props_can(args->argv[0])){
    init.config.traceSql =
      cwal_value_get_bool(cwal_prop_get(args->argv[0],
                                        "traceSql", 8));
  }
  rc = fsl_cx_init( &f, &init );
  if(rc){
    fsl_cx_finalize( f );
    return cb_toss(args, FSL_RC_ERROR,
                   "Fossil context initialization failed "
                   "with code #%d (%s).", rc,
                   fsl_rc_cstr(rc));
  }
  v = cwal_new_native_value(args->engine, f,
                            cwal_finalizer_f_fsl_cx,
                            FSL_TYPEID(fsl_cx));
  if(!v){
    fsl_cx_finalize( f );
    return CWAL_RC_OOM;
  }
  cwal_value_prototype_set( v, fsl_cx_prototype(se) );
  fsl_cx_db_prop( f, v, se, 1 ) /* initialize this->db */;
  *rv  = v;
  return 0;
}

/**
   If parent contains a Db property with the given name, that property
   is disconnected from its native and removed from parent, but its Db
   finalizer is not called (ownership lies elsewhere for the fsl_db
   parts of properties). The db's Fossil context pointer (if any) is
   also set to 0.

*/
static void cb_fsl_clear_handles(cwal_value * parent,
                                 char const * dbName){
  cwal_engine * e = cwal_value_engine(parent);
  cwal_size_t const dbNameLen = cwal_strlen(dbName);
  cwal_value * v = e ? cwal_prop_get(parent, dbName, dbNameLen) : NULL;
  assert(e);
  if(v){
    cwal_native * nat = cwal_value_native_part(e, v, FSL_TYPEID(fsl_db));
    fsl_db * db = nat ? cwal_native_get( nat, FSL_TYPEID(fsl_db) ) : NULL;
    assert(nat ? !!db : 1);
    if(db){
      db->f = 0 /* avoid holding a stale pointer. */;
      cwal_native_clear(nat, 0);
      cwal_prop_unset(parent, dbName, dbNameLen);
    }
  }
}

static int cb_fsl_cx_close( cwal_callback_args const * args,
                         cwal_value **rv ){
  cwal_value * dbNs;
  THIS_F;
  /* It's important that we clear the Value/Native bindings
     to all of the context's databases because clients can do this:

     var db = f.db.repo;
     f.close();'
     db.something(...)

     Which, if we're not careful here, can lead to them having
     not only a stale fsl_db handle, but one which points back
     to a stale fsl_cx pointer.

     Thank you, valgrind.
  */
  dbNs = fsl_cx_db_prop(f, natV, se, 0);
  if(dbNs){
    cb_fsl_clear_handles(dbNs, "repo");
    cb_fsl_clear_handles(dbNs, "checkout");
    cb_fsl_clear_handles(dbNs, "config");
    cb_fsl_clear_handles(natV, "db");
  }
  if(fsl_cx_db_config(f)){
    fsl_config_close(f);
  }
  if(fsl_cx_db_ckout(f)){
    fsl_ckout_close(f);
    /* also closes its repo db */
  }else if(fsl_cx_db_repo(f)){
    fsl_repo_close(f);
  }
  *rv = args->self;
  return 0;
}


static int cb_fsl_cx_finalize( cwal_callback_args const * args,
                              cwal_value **rv ){
  THIS_F;
  if(fsl_cx_db_prop(f, args->self, se, 0)){
    cb_fsl_cx_close(args, rv);
  }
  cwal_native_clear( nat, 1 );
  return 0;
}

static int cb_fsl_cx_user_name( cwal_callback_args const * args,
                             cwal_value **rv ){
  char const * uname;
  THIS_F;
  if(! (uname = fsl_cx_user_get(f)) ){
    uname = fsl_guess_user_name();
  }
  *rv = (uname && *uname)
    ? cwal_new_string_value(args->engine, uname, cwal_strlen(uname))
    : cwal_value_undefined();
  return *rv ? 0 : CWAL_RC_OOM;
}



static int cb_fsl_cx_resolve_sym( cwal_callback_args const * args,
                                  cwal_value **rv,
                                  char toUuid ){
  char * uuid = NULL;
  char const * sym;
  int rc;
  cwal_int_t argRid = 0;
  fsl_id_t rid = 0;
  THIS_F;
  sym = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if((!sym || !*sym) && args->argc){
    argRid = cwal_value_get_integer(args->argv[0]);
  }
  if(argRid<=0 && (!sym || !*sym)){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting a positive integer or non-empty "
                   "string argument.");
  }
  rc = toUuid
    ? fsl_sym_to_uuid( f, sym, FSL_SATYPE_ANY, &uuid, &rid )
    : fsl_sym_to_rid( f, sym, FSL_SATYPE_ANY, &rid );
  if(rc){
    /* Undecided: throw or return undefined if no entry found? */
#if 1
    if(FSL_RC_NOT_FOUND==rc){
      *rv = cwal_value_undefined();
      rc = 0;
    }else{
      rc = cb_toss_fsl(args, f);
    }
#else
    rc = cb_toss_fsl(args, f);
#endif
  }else{
    if(toUuid){
      assert(uuid);
      *rv = cwal_new_string_value(args->engine, uuid, cwal_strlen(uuid));
      fsl_free(uuid);
      if(!*rv){
        rc = CWAL_RC_OOM;
      }
    }else{
      assert(!uuid);
      assert(rid>0);
      *rv = cwal_new_integer(args->engine, (cwal_int_t)rid);
      if(!*rv) rc = CWAL_RC_OOM;
    }
  }
  return rc;
}


static int cb_fsl_cx_sym2uuid( cwal_callback_args const * args,
                                   cwal_value **rv ){
  return cb_fsl_cx_resolve_sym(args, rv, 1);
}

static int cb_fsl_cx_sym2rid( cwal_callback_args const * args,
                              cwal_value **rv ){
  return cb_fsl_cx_resolve_sym(args, rv, 0);
}

static int cb_fsl_cx_content_get( cwal_callback_args const * args,
                                  cwal_value **rv ){
  int rc;
  fsl_id_t rid = 0;
  char const * sym = NULL;
  fsl_buffer fbuf = fsl_buffer_empty;
  THIS_F;
  if(!args->argc) goto usage;
  else{
    cwal_value * arg = args->argv[0];
    if(cwal_value_is_integer(arg)){
      rid = (fsl_id_t)cwal_value_get_integer(arg);
      if(rid<=0) goto usage;
    }else{
      sym = cwal_value_get_cstr(arg, NULL);
      if(!sym || !*sym) goto usage;
    }
  }
  rc = (rid>0)
    ? fsl_content_get(f, rid, &fbuf)
    : fsl_content_get_sym(f, sym, &fbuf);
  if(rc){
    assert(f->error.code);
    rc = cb_toss_fsl(args, f);
  }else{
    cwal_buffer * cbuf = cwal_new_buffer(args->engine, 0);
    if(!cbuf) rc = CWAL_RC_OOM;
    else{
      cwal_value * cbufV = cwal_buffer_value(cbuf);
      cwal_value_ref(cbufV);
      rc = fsl_buffer_to_cwal_buffer(args->engine, &fbuf, cbuf);
      if(rc){
        cwal_value_unref(cbufV);
      }else{
        cwal_value_unhand(cbufV);
        *rv = cbufV;
      }
    }
  }
  fsl_buffer_clear(&fbuf);
  return rc;
  usage:
  return cb_toss(args, FSL_RC_MISUSE,
                 "Expecting one symbolic name (string) or "
                 "RID (positive integer) argument.");
}

typedef struct card_visitor_F_to_object {
  cwal_engine * e;
  cwal_array * dest;
} card_visitor_F_to_object;

/**
   fsl_card_F_visitor_f() impl which converts fc to a cwal Object
   representation and appends it to
   ((card_visitor_F_to_object*)state)->dest.
*/
static int fsl_card_F_visitor_fc_to_object(fsl_card_F const * fc,
                                           void * state){
  card_visitor_F_to_object * vs = (card_visitor_F_to_object*)state;
  int rc;
  cwal_value * ov = cwal_new_object_value(vs->e);
  char const * typeLabel = NULL;
  if(!ov) return CWAL_RC_OOM;
  cwal_value_ref(ov);
  rc = cwal_array_append(vs->dest, ov);
  cwal_value_unref(ov);
  if(rc){
    return rc;
  }
#define vset(K,KL,V) cwal_prop_set(ov, K, KL, V)
  vset("name", 4, cwal_new_string_value(vs->e, fc->name,
                                        cwal_strlen(fc->name)));
  if(fc->priorName){
    vset("priorName", 9,
         cwal_new_string_value(vs->e, fc->priorName,
                               cwal_strlen(fc->priorName)));
  }
  if(fc->uuid){
    vset("uuid", 4, cwal_new_string_value(vs->e, fc->uuid,
                                          cwal_strlen(fc->uuid)));
  }
  switch(fc->perm){
    case FSL_FILE_PERM_EXE: typeLabel = "x"; break;
    case FSL_FILE_PERM_LINK: typeLabel = "l"; break;
    default: typeLabel = "f";
  }
  vset("perm", 4, cwal_new_string_value(vs->e, typeLabel, 1)
       /* good case for cwal's string interning! */);
#undef vset
  return rc;
}

/**
   Converts a fsl_deck to an Object representation, assigning *rv
   to the new object (new, with no live references).

   Must return a CWAL_RC value, not FSL_RC!
*/
static int s2_deck_to_object( cwal_engine * e, fsl_deck * mf,
                              char includeBaselineObj,
                              cwal_value ** rv ){
  int rc = 0;
  cwal_value * ov = NULL;
  cwal_value * card = NULL;
  cwal_array * ar = NULL;
  cwal_value * av = NULL;
  cwal_size_t i;
  assert(e);
  assert(mf);
  assert(mf->uuid);
  assert(mf->rid);
  rc = fsl_deck_F_rewind(mf);
  if(rc) goto end;
  ov = cwal_new_object_value(e);
  if(!ov) return CWAL_RC_OOM;
  cwal_value_ref(ov);
#define vset2(OBJ,K,V) cwal_prop_set(OBJ, K, cwal_strlen(K), V)
#define dset(K,V) vset2(ov,K,V)
#define cset(K,V) vset2(card,K,V)
#define vornull(VP,V) ((VP) ? (V) : cwal_value_null())
#define strval2(CSTR,LEN) vornull((CSTR), cwal_new_string_value(e, (char const *)(CSTR), (cwal_size_t)(LEN)))
#define strval(CSTR) strval2((CSTR), cwal_strlen(CSTR))
  dset("type", cwal_new_integer(e, mf->type));
  dset("rid", cwal_new_integer(e, (cwal_int_t)mf->rid));
  dset("uuid", strval2(mf->uuid, cwal_strlen(mf->uuid)));
  
  if(mf->A.src){
    card = cwal_new_object_value(e);
    dset("A", card);
    cset("name", strval(mf->A.name));
    cset("tgt", strval(mf->A.tgt));
    cset("uuid", strval2(mf->A.src, cwal_strlen(mf->A.src)));
  }
  if(mf->B.uuid){
    card = cwal_new_object_value(e);
    dset("B", card);
    cset("uuid", strval2(mf->B.uuid,cwal_strlen(mf->B.uuid)));
    if(includeBaselineObj){
      rc = fsl_deck_F_rewind(mf) /* load baseline if needed */;
      if(!rc){
        cwal_value * v = NULL;
        assert(mf->B.baseline);
        rc = s2_deck_to_object( e, mf->B.baseline, 0, &v );
        if(v){
          cset("baseline", v);
        }
        else{
          assert(rc);
          goto end;
        }
      }
    }
  }
  if(mf->C){
    dset("C", strval(mf->C));
  }
  if(mf->D > 0){
    dset("D", cwal_new_double(e, mf->D));
  }
  if(mf->E.julian > 0){
    card = cwal_new_object_value(e);
    dset("E", card);
    cset("julian", cwal_new_double(e, mf->E.julian));
    cset("uuid", strval2(mf->E.uuid, cwal_strlen(mf->E.uuid)));
  }
  if(mf->F.used || mf->B.uuid){
    card_visitor_F_to_object vstate;
    cwal_size_t const reserveSize = mf->F.used
      + (mf->B.baseline ? mf->B.baseline->F.used : 0);
    assert(fsl_card_is_legal(mf->type, 'F'));
    ar = cwal_new_array(e);
    card = av = cwal_array_value(ar);
    dset("F", card);
    cwal_array_reserve(ar, reserveSize);
    vstate.e = e;
    vstate.dest = ar; 
    rc = fsl_deck_F_foreach(mf, fsl_card_F_visitor_fc_to_object,
                            &vstate);
    if(rc) goto end;
  }/* end of F-card */
  if(mf->G){
    dset("G", strval2(mf->G, cwal_strlen(mf->G)));
  }
  if(mf->H){
    dset("H", strval(mf->H));
  }
  if(mf->I){
    dset("I", strval2(mf->I, cwal_strlen(mf->I)));
  }
#define append(V) cwal_array_append(ar, (V))
  if(mf->J.used){
    ar = cwal_new_array(e);
    card = av = cwal_array_value(ar);
    dset("J", card);
    append(strval2("TODO",4));
    for(i = 0; i < mf->J.used; ++i ){
      /* TODO */
    }
  }
  if(mf->K){
    dset("K", strval2(mf->K, cwal_strlen(mf->K)));
  }
  if(mf->L){
    dset("L", strval(mf->L));
  }
  
  if(mf->M.used){
    ar = cwal_new_array(e);
    card = av = cwal_array_value(ar);
    dset("M", card);
    for(i = 0; i < mf->M.used; ++i ){
      fsl_uuid_cstr uuid = (fsl_uuid_cstr)mf->M.list[i];
      assert(uuid);
      append(strval2(uuid,cwal_strlen(uuid)));
    }
  }

  if(mf->P.used){
    ar = cwal_new_array(e);
    card = av = cwal_array_value(ar);
    dset("P", card);
    for(i = 0; i < mf->P.used; ++i ){
      fsl_uuid_cstr uuid = (fsl_uuid_cstr)mf->P.list[i];
      assert(uuid);
      append(strval2(uuid,cwal_strlen(uuid)));
    }
  }

  if(mf->Q.used){
    ar = cwal_new_array(e);
    card = av = cwal_array_value(ar);
    dset("Q", card);
    append(strval2("TODO",4));
    for(i = 0; i < mf->P.used; ++i ){
      fsl_card_Q const * qc = (fsl_card_Q const *)mf->Q.list[i];
      assert(qc);
      /* TODO */
    }
  }
  
  if(mf->U){
      dset("U", strval(mf->U));
  }

  if(mf->W.used){
    dset("W", vornull(mf->W.used, strval2(mf->W.mem,mf->W.used)));
  }else if(0 < fsl_card_is_legal(mf->type, 'W') /* required card */){
    /* corner-case: empty wiki page */
    dset("W", strval2("",0));
  }

#undef append
#undef strval
#undef strval2
#undef vornull
#undef cset
#undef dset
#undef vset2
  end:
  if(!rc && rv){
    assert(ov);
    cwal_value_unhand(ov);
    *rv = ov;
  }
  else if(ov){
    cwal_value_unref(ov);
  }
  return rc;
}

/**
   Script usage:

   const deck = Fossil.loadManifest(
       version [, includeBaselineObject]

   The deck object's struct matches fsl_deck pretty
   closely.
*/
static int cb_fsl_cx_deck_load( cwal_callback_args const * args,
                                cwal_value **rv ){
  int rc;
  fsl_deck mf = fsl_deck_empty;
  fsl_id_t rid = 0;
  char const * sym = NULL;
  fsl_satype_e mfType = FSL_SATYPE_ANY;
  char includeBaselineObj;
  THIS_F;
  if(!args->argc) goto usage;
  else if(cwal_value_is_number(args->argv[0])){
    rid = (fsl_id_t)cwal_value_get_integer(args->argv[0]);
    if(rid<=0) goto usage;
  }else{
    sym = cwal_value_get_cstr(args->argv[0], NULL);
    if(!sym || !*sym) goto usage;
  }
  includeBaselineObj = (args->argc>1)
    ? cwal_value_get_bool(args->argv[1])
    : 0;
  /* TODO: script mapping for fsl_satype_e values for 3nd arg.
   */
  rc = sym
    ? fsl_deck_load_sym(f, &mf, sym, mfType)
    : fsl_deck_load_rid(f, &mf, rid, mfType)
    ;
  if(rc){
    if(f->error.code) rc = cb_toss_fsl(args, f);
    else{
      rc = sym
        ? cb_toss(args, rc, "Loading manifest '%s' failed with code %d/%s",
                  sym, rc, fsl_rc_cstr(rc))
        : cb_toss(args, rc, "Loading manifest RID %d failed with code %d/%s",
                  (int)rid, rc, fsl_rc_cstr(rc));
    }
    goto end;
  }

  rc = s2_deck_to_object(args->engine, &mf,
                         includeBaselineObj, rv);
  end:
  fsl_deck_finalize(&mf);
  return rc;
  usage:
  return cb_toss(args, FSL_RC_MISUSE,
                 "Expecting one symbolic name (string) or "
                 "RID (positive integer) argument.");
}

/**
   Script usage:

   var x = thisFunc(fromVersion, toVersion [, Object options])

   Options:

   integer contextLines

   integer sbsWidth

   bool html

   bool text

   bool inline

   bool invert

   Result is a Buffer containing the generated diff.
*/
static int cb_fsl_cx_adiff( cwal_callback_args const * args,
                            cwal_value **rv ){
  char const * sym;
  fsl_id_t rid1, rid2;
  cwal_value * arg;
  int diffFlags = 0;
  int rc;
  short contextLines = 3;
  short sbsWidth = 0;
  fsl_buffer c1 = fsl_buffer_empty;
  fsl_buffer c2 = fsl_buffer_empty;
  fsl_buffer delta = fsl_buffer_empty;
  THIS_F;
  if(args->argc<2){
    misuse:
    return cb_toss(args, FSL_RC_MISUSE, "Expecting two artifact ID arguments.");
  }
  /* The v1 arg... */
  arg = args->argv[0];
  if(cwal_value_is_integer(arg)){
    rid1 = (fsl_id_t)cwal_value_get_integer(arg);
  }else if(!(sym = cwal_value_get_cstr(arg,NULL))){
    goto misuse;
  }else{
    rc = fsl_sym_to_rid(f, sym, FSL_SATYPE_ANY, &rid1);
    if(rc) return cb_toss_fsl(args, f);
  }

  /* The v2 arg... */
  arg = args->argv[1];
  if(cwal_value_is_integer(arg)){
    rid2 = (fsl_id_t)cwal_value_get_integer(arg);
  }else if(!(sym = cwal_value_get_cstr(arg,NULL))){
    goto misuse;
  }else{
    rc = fsl_sym_to_rid(f, sym, FSL_SATYPE_ANY, &rid2);
    if(rc) return cb_toss_fsl(args, f);
  }

  assert(rid1>0);
  assert(rid2>0);

  rc = fsl_content_get(f, rid1, &c1);
  if(!rc) rc = fsl_content_get(f, rid2, &c2);
  if(rc){
    rc = cb_toss_fsl(args, f);
    goto end;
  }
  diffFlags = FSL_DIFF_LINENO | FSL_DIFF_SIDEBYSIDE;
  if((args->argc>2) && cwal_props_can((arg = args->argv[2]))){
    do{
      /* Collect diff flags from object */
      cwal_value * v;
      v = cwal_prop_get(arg, "contextLines", 12);
      if(v) contextLines = (short)cwal_value_get_integer(v);
      v = cwal_prop_get(arg, "html", 4);
      if(v && cwal_value_get_bool(v)){
        diffFlags |= FSL_DIFF_HTML;
      }
      v = cwal_prop_get(arg, "inline", 6);
      if(v && cwal_value_get_bool(v)){
        diffFlags &= ~FSL_DIFF_SIDEBYSIDE;
        sbsWidth = 0;
      }
      v = cwal_prop_get(arg, "invert", 6);
      if(v && cwal_value_get_bool(v)){
        diffFlags |= FSL_DIFF_INVERT;
      }
      v = cwal_prop_get(arg, "sbsWidth", 8);
      if(v){
        sbsWidth = (short)cwal_value_get_integer(v);
      }
      v = cwal_prop_get(arg, "text", 4);
      if(v && cwal_value_get_bool(v)){
        diffFlags &= ~FSL_DIFF_HTML;
      }
    } while(0);
  }

  rc = fsl_diff_text(&c1, &c2, fsl_output_f_buffer, &delta,
                     contextLines, sbsWidth, diffFlags);
  if(rc){
    if(FSL_RC_OOM==rc) rc = CWAL_RC_OOM;
    else rc = cb_toss(args, rc, "Error %s while creating diff.",
                      fsl_rc_cstr(rc));
  }else{
    cwal_value * v = cwal_new_buffer_value(args->engine, 0);
    cwal_buffer * bv = v ? cwal_value_buffer_part(args->engine, v) : NULL;
    if(!v){
      rc = CWAL_RC_OOM;
      goto end;
    }else{
      cwal_value_ref(v);
      assert(!bv->mem);
      rc = fsl_buffer_to_cwal_buffer(args->engine, &delta, bv);
      if(rc){
        cwal_value_unref(v);
      }else{
        assert(bv->used == delta.used);
        cwal_value_unhand(v);
        *rv = v;
      }
    }
  }
  end:
  fsl_buffer_clear(&c1);
  fsl_buffer_clear(&c2);
  fsl_buffer_clear(&delta);
  return rc;
}


cwal_value * fsl_stmt_prototype( s2_engine * se ){
  int rc = 0;
  cwal_value * proto;
  cwal_value * v;
  char const * pKey = "Fossil.Stmt";
  proto = s2_prototype_stashed(se, pKey);
  if(proto) return proto;
  proto = cwal_new_object_value(se->e);
  if(!proto){
    rc = CWAL_RC_OOM;
    goto end;
  }
  rc = s2_prototype_stash( se, pKey, proto );
  if(rc) goto end;

#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                                           \
  VCHECK;                                                   \
  cwal_value_ref(v);                                        \
  rc = cwal_prop_set( proto, NAME, cwal_strlen(NAME), v );  \
  cwal_value_unref(v);                                      \
  if(rc) {goto end;} (void)0

  v = cwal_new_string_value(se->e, "Stmt", 4);
  SET("__typename");

  {
    s2_func_def const funcs[] = {
      S2_FUNC2("stepObject", cb_fsl_stmt_step_object),
      S2_FUNC2("stepArray", cb_fsl_stmt_step_array),
      S2_FUNC2("step", cb_fsl_stmt_step),
      S2_FUNC2("rowToObject", cb_fsl_stmt_row_to_object),
      S2_FUNC2("rowToArray", cb_fsl_stmt_row_to_array),
      S2_FUNC2("reset", cb_fsl_stmt_reset),
      S2_FUNC2("get", cb_fsl_stmt_get),
      S2_FUNC2("finalize", cb_fsl_stmt_finalize),
      S2_FUNC2("bind", cb_fsl_stmt_bind),
      s2_func_def_empty_m
    };
    rc = s2_install_functions(se, proto, funcs, 0);
  }
  {
    /* Stmt.each() impl. */
    char const * src =
      "proc(_){"
        "affirm typeinfo(iscallable _) && typeinfo(iscallable _.call); "
        "while(this.step()) false === _.call(this) && break;"
        "return this"
      "}";
      int const srcLen = (int)cwal_strlen(src);
      rc = s2_set_from_script(se, src, srcLen, proto, "each", 4);
  }

#undef SET
#undef VCHECK
  end:
  return rc ? NULL : proto;
}

cwal_value * fsl_db_prototype( s2_engine * se ){
  int rc = 0;
  cwal_value * proto;
  cwal_value * v;
  char const * pKey = "Fossil.Db";
  cwal_engine * e;
  proto = s2_prototype_stashed(se, pKey);
  if(proto) return proto;
  e = s2_engine_engine(se);
  /* cwal_value * fv; */
  assert(se && e);
  proto = cwal_new_object_value(e);
  if(!proto){
    rc = CWAL_RC_OOM;
    goto end;
  }
  rc = s2_prototype_stash( se, pKey, proto );
  if(rc) goto end;
#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                                           \
  VCHECK;                                                   \
  cwal_value_ref(v);                                        \
  rc = cwal_prop_set( proto, NAME, cwal_strlen(NAME), v );  \
  cwal_value_unref(v);                                      \
  if(rc) {goto end; } (void)0
  
  v = cwal_new_string_value(e, "Db", 2);
  SET("__typename");

  {
    s2_func_def const funcs[] = {
      S2_FUNC2("transactionState", cb_fsl_db_trans_state),
      S2_FUNC2("transaction", cb_fsl_db_trans),
      S2_FUNC2("selectValues", cb_fsl_db_select_values),
      S2_FUNC2("selectValue", cb_fsl_db_select_value),
      S2_FUNC2("rollback", cb_fsl_db_trans_rollback),
      S2_FUNC2("prepare", cb_fsl_db_prepare),
      S2_FUNC2("open", cb_fsl_db_ctor),
      S2_FUNC2("lastInsertId", cb_fsl_db_last_insert_id),
      S2_FUNC2("getName", cb_fsl_db_name),
      S2_FUNC2("getFilename", cb_fsl_db_filename),
      S2_FUNC2("execMulti", cb_fsl_db_exec_multi),
      S2_FUNC2("exec", cb_fsl_db_exec),
      S2_FUNC2("each", cb_fsl_db_each),
      S2_FUNC2("commit", cb_fsl_db_trans_commit),
      S2_FUNC2("close", cb_fsl_db_finalize),
      S2_FUNC2("begin", cb_fsl_db_trans_begin),
      s2_func_def_empty_m
    };
    rc = s2_install_functions(se, proto, funcs, 0);
    if(rc) goto end;
  }
  v = cwal_prop_get(proto, "open", 4);
  assert(v && "We just installed this!");
  rc = s2_ctor_method_set(se, proto, cwal_value_get_function(v));
  if(rc) goto end;

#if 0
  /* TODO: port in stepTuple() from the main sqlite3 s2 mod
     and then add these... */     
  {
    /* selectRow(sql,bind,asTuple) impl. */
    char const * src =
      "proc(s,b,t){"/*sql, bind, asTuple*/
        "return this.prepare(s).bind(b)"
        "[t?'stepTuple':'stepObject']()"
        /* return will indirectly finalize() the anonymous stmt. */
      "}";
    rc = s2_set_from_script(se, src, (int)cwal_strlen(src),
                            proto, "selectRow", 9);
    if(rc) goto end;
    /* selectRows(sql,bind,asTuple) impl: */
    src = "proc(s,b,t){"/*sql, bind, asTuple*/
        "const S = this.prepare(s).bind(b), m = t?'stepTuple':'stepObject', rc=[];"
        "var v; while(v=S[m]())rc[]=v; S.finalize();"
        "return rc;"
      "}";
    rc = s2_set_from_script(se, src, (int)cwal_strlen(src),
                            proto, "selectRows", 10);
  }
#endif

  v = fsl_stmt_prototype(se);
  VCHECK;
  if( (rc = cwal_prop_set_with_flags( proto, "Stmt", 4, v,
                                      CWAL_VAR_F_CONST)) ){
    goto end;
  }

#undef SET
#undef VCHECK
  end:
  return rc ? NULL : proto;
}

cwal_value * fsl_cx_prototype( s2_engine * se ){
  int rc = 0;
  cwal_value * proto;
  cwal_value * v;
  char const * pKey = "Fossil.Context";
  cwal_engine * e;
  proto = s2_prototype_stashed(se, pKey);
  if(proto) return proto;
  e = s2_engine_engine(se);
  assert(se && e);
  proto = cwal_new_object_value(e);
  if(!proto){
    rc = CWAL_RC_OOM;
    goto end;
  }
  rc = s2_prototype_stash( se, pKey, proto );
  if(rc) goto end;
#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                                           \
  VCHECK;                                                   \
  cwal_value_ref(v);                                        \
  rc = cwal_prop_set( proto, NAME, cwal_strlen(NAME), v );  \
  cwal_value_unref(v);                                      \
  if(rc) {goto end; } (void)0
  
  v = cwal_new_string_value(e, "Context", 7);
  SET("__typename");
  {
    s2_func_def const funcs[] = {
      S2_FUNC2("symToUuid", cb_fsl_cx_sym2uuid),
      S2_FUNC2("symToRid", cb_fsl_cx_sym2rid),
      S2_FUNC2("openRepo", cb_fsl_repo_open),
      S2_FUNC2("openDb", cb_fsl_db_ctor),
      S2_FUNC2("openConfig", cb_fsl_config_open),
      S2_FUNC2("openCheckout", cb_fsl_ckout_open_dir),
      S2_FUNC2("loginCookieName", cb_fsl_login_cookie_name),
      S2_FUNC2("loadManifest",cb_fsl_cx_deck_load),
      S2_FUNC2("loadBlob", cb_fsl_cx_content_get),
      S2_FUNC2("getUserName", cb_fsl_cx_user_name),
      S2_FUNC2("finalize", cb_fsl_cx_finalize),
      /* S2_FUNC2("closeConfig", cb_fsl_config_close), */
      S2_FUNC2("close", cb_fsl_cx_close),
      S2_FUNC2("artifactDiff", cb_fsl_cx_adiff),
      s2_func_def_empty_m
    };
    rc = s2_install_functions(se, proto, funcs, 0);
    if(rc) goto end;
  }
  rc = s2_ctor_callback_set(se, proto, cb_fsl_cx_ctor);

#undef SET
#undef VCHECK
  end:
  return rc ? NULL : proto;
}


/**
   Script usage:


   const m = globMatches("*.*", "some.string")

   Or:

   const m = "*.*".globMatches(someString)

   (The second form is not installed by default!)

 */
static int cb_strglob( cwal_callback_args const * args,
                       cwal_value **rv ){
  int rc = 0;
  char const * glob =
    (args->argc>0) ? cwal_value_get_cstr(args->argv[0], NULL) : NULL;
  char const * str =
    (args->argc>1) ? cwal_value_get_cstr(args->argv[1], NULL) : NULL;
  char const * sSelf = cwal_value_get_cstr(args->self, NULL);
  if(sSelf){
    str = glob;
    glob = sSelf;
  }
  if(!glob){
    rc = cb_toss(args, FSL_RC_MISUSE,
                 "Expecting (glob,string) arguments "
                 "OR stringInstance.thisFunc(otherString) "
                 "usage.");
  }
  else if(!str){
    *rv = cwal_value_false();
  }else{
    *rv = fsl_str_glob(glob, str)
      ? cwal_value_true() : cwal_value_false();
  }
  return rc;
}

static int cb_is_uuid( cwal_callback_args const * args,
                       cwal_value **rv ){
  cwal_size_t slen = 0;
  char const * str =
    (args->argc>0) ? cwal_value_get_cstr(args->argv[0], &slen) : NULL;
  *rv = (str && fsl_is_uuid(str))
    ? cwal_value_true()
    : cwal_value_false();
  return 0;
}

static fsl_timer_state RunTimer = fsl_timer_state_empty_m;

/**
   Retuns the number of microseconds of _CPU_ time (not wall clock
   time) used since s2_shell_extend() was run.
*/
static int cb_run_timer_fetch( cwal_callback_args const * args,
                               cwal_value **rv ){
  uint64_t t = fsl_timer_fetch(&RunTimer);
  *rv = cwal_new_integer(args->engine, (cwal_int_t)t);
  return *rv ? 0 : CWAL_RC_OOM;
}

static int cb_fsl_delta_create( cwal_callback_args const * args,
                                cwal_value **rv ){
  char const * s1 = NULL;
  char const * s2 = NULL;
  cwal_size_t len1, len2;
  fsl_size_t outLen = 0;
  cwal_buffer * cb;
  cwal_value * cbV = 0;
  int rc;
  if(args->argc>1){
    s1 = cwal_value_get_cstr(args->argv[0], &len1);
    s2 = cwal_value_get_cstr(args->argv[1], &len2);
  }
  if(!s1 || !s2){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting two non-empty string/buffer "
                   "arguments.");
  }
  cb = cwal_new_buffer(args->engine, len2+61);
  if(!cb) return CWAL_RC_OOM;
  cbV = cwal_buffer_value(cb);
  cwal_value_ref(cbV);
  assert(cb->capacity > (len2+60));
  rc = fsl_delta_create( (unsigned char const *)s1, (fsl_size_t)len1,
                         (unsigned char const *)s2, (fsl_size_t)len2,
                         cb->mem, &outLen);
#if 0
  if(!rc){
    /* Resize the buffer to fit. */
    rc = cwal_buffer_resize(args->engine, cb, (cwal_size_t)outLen);
  }
#endif
  if(rc) cwal_value_unref(cbV);
  else{
    cwal_value_unhand(cbV);
    *rv = cbV;
  }
  return rc;
}

static int cb_fsl_delta_applied_len( cwal_callback_args const * args,
                                     cwal_value **rv ){
  char const * src;
  cwal_size_t srcLen;
  fsl_size_t appliedLen = 0;
  int rc;
  src = (args->argc>0)
    ? cwal_value_get_cstr(args->argv[0], &srcLen)
    : NULL;
  if(!src){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting one delta string "
                   "argument.");
  }
  rc = fsl_delta_applied_size((unsigned char const *)src,
                              (fsl_size_t)srcLen, &appliedLen);
  if(rc){
    return cb_toss(args, rc,
                   "Input does not appear to be a "
                   "delta. Error #%d (%s).",
                   rc, fsl_rc_cstr(rc));
  }else{
    *rv = cwal_new_integer(args->engine,
                           (cwal_int_t)appliedLen);
    return *rv ? 0 : CWAL_RC_OOM;
  }
}

static int cb_fsl_delta_apply( cwal_callback_args const * args,
                               cwal_value **rv ){
  char const * s1 = NULL;
  char const * s2 = NULL;
  char const * src;
  char const * delta;
  cwal_size_t len1, len2, srcLen, dLen;
  fsl_size_t appliedLen = 0;
  cwal_buffer * cb;
  cwal_value * cbV;
  int rc;
  if(args->argc>1){
    s1 = cwal_value_get_cstr(args->argv[0], &len1);
    s2 = cwal_value_get_cstr(args->argv[1], &len2);
  }
  if(!s1 || !s2){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting two non-empty string/buffer "
                   "arguments.");
  }
  rc = fsl_delta_applied_size((unsigned char const *)s2,
                              (fsl_size_t)len2, &appliedLen);
  if(!rc){
    src = s1;
    srcLen = len1;
    delta = s2;
    dLen = len2;
  }else{ /* Check if the user perhaps swapped the args. */
    rc = fsl_delta_applied_size((unsigned char const *)s1,
                                (fsl_size_t)len1, &appliedLen);
    if(rc){
      return cb_toss(args, FSL_RC_MISUSE,
                     "Expecting a delta string/buffer as one "
                     "of the first two arguments.");
    }
    src = s2;
    srcLen = len2;
    delta = s1;
    dLen = len1;
  }
  cb = cwal_new_buffer(args->engine, appliedLen+1);
  if(!cb) return CWAL_RC_OOM;
  cbV = cwal_buffer_value(cb);
  cwal_value_ref(cbV);
  assert(cb->capacity > appliedLen);
  rc = fsl_delta_apply( (unsigned char const *)src, (fsl_size_t)srcLen,
                        (unsigned char const *)delta, (fsl_size_t)dLen,
                        cb->mem);
  if(!rc){
    assert(0==cb->mem[appliedLen]);
#if 0
    if(cb->capacity > (cb->used * 4 / 3)){
      rc = cwal_buffer_resize(args->engine, cb, (cwal_size_t)appliedLen);
    }
#endif
  }else{
    rc = cb_toss(args, rc,
                 "Application of delta failed with "
                 "code #%d (%s).", rc,
                 fsl_rc_cstr(rc));
  }
  if(rc) cwal_value_unref(cbV);
  else {
    cwal_value_unhand(cbV);
    *rv = cbV;
  }
  return rc;
}

static int fsl_add_delta_funcs( cwal_engine * e, cwal_value * ns ){
  cwal_value * func;
  cwal_value * v;
  int rc;
  func = cwal_new_object_value(e);
  if(!func) return CWAL_RC_OOM;
  cwal_value_ref(func);
  rc = cwal_prop_set(ns, "delta", 5, func);
  cwal_value_unref(func);
  if(rc){
    return rc;
  }
#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                                              \
  VCHECK;                                                         \
  cwal_value_ref(v);                                               \
  rc = cwal_prop_set( func, NAME, cwal_strlen(NAME), v );          \
  cwal_value_unref(v);                                             \
  if(rc){goto end;}(void)0
#define FUNC(NAME,FP)                    \
  v = cwal_new_function_value( e, FP, 0, 0, 0 );  \
  SET(NAME)
  FUNC("appliedLength", cb_fsl_delta_applied_len);
  FUNC("apply", cb_fsl_delta_apply);
  FUNC("create", cb_fsl_delta_create);
#undef SET
#undef FUNC
#undef VCHECK
  end:
  return rc;
}

/**
   Script usage:

   string canonicalName(string filename [, bool keepSlash = false])
   string canonicalName(string rootDirName, string filename[, bool keepSlash = false])
*/
static int cb_fs_file_canonical( cwal_callback_args const * args,
                                 cwal_value **rv ){
  int rc;
  char const * fn = 0;
  char const * root = 0;
  fsl_buffer buf = fsl_buffer_empty;
  char slash = 0;
  if(1==args->argc){
    fn = cwal_value_get_cstr(args->argv[0], 0);
  }else if(args->argc>1){
    if(cwal_value_is_bool(args->argv[1])){
      fn = cwal_value_get_cstr(args->argv[0], 0);
      slash = cwal_value_get_bool(args->argv[1]) ? 1 : 0;
    }else{
      root = cwal_value_get_cstr(args->argv[0], 0);
      fn = cwal_value_get_cstr(args->argv[1], 0);
      if(args->argc>2) slash = cwal_value_get_bool(args->argv[2]);
    }
  }
  if(!fn) return cb_toss(args, FSL_RC_MISUSE,
                         "Expecting a non-empty string "
                         "argument.");
  rc = fsl_file_canonical_name2( root, fn, &buf, slash );
  if(FSL_RC_OOM==rc) rc = CWAL_RC_OOM;
  else if(!rc){
    *rv = cwal_new_string_value(args->engine,
                                buf.used ? (char const *)buf.mem : 0,
                                (cwal_size_t)buf.used);
    if(!*rv){
      rc = CWAL_RC_OOM;
    }
  }
  fsl_buffer_clear(&buf);
  return rc;
}

/**
   Script binding to fsl_file_dirpart():

   var x = thisFunc(path, leaveSlash=true)
 */
static int cb_fs_file_dirpart( cwal_callback_args const * args,
                               cwal_value **rv ){
  int rc;
  char const * fn;
  fsl_buffer buf = fsl_buffer_empty;
  cwal_size_t fnLen = 0;
  char leaveSlash;
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], &fnLen)
    : NULL;
  if(!fn || !*fn) return cb_toss(args, FSL_RC_MISUSE,
                                 "Expecting non-empty string "
                                 "argument.");
  leaveSlash = args->argc>1
    ? cwal_value_get_bool(args->argv[1])
    : 1;

  rc = fsl_file_dirpart(fn, (fsl_size_t)fnLen, &buf, leaveSlash);
  if(FSL_RC_OOM==rc) rc = CWAL_RC_OOM;
  else if(!rc){
    *rv = cwal_new_string_value(args->engine,
                                buf.used ? (char const *)buf.mem : 0,
                                buf.used);
    if(!*rv){
      rc = CWAL_RC_OOM;
    }
  }
  fsl_buffer_clear(&buf);
  return rc;
}

/**
   Script usage:

   const size = Fossil.file.size(filename)
*/
static int cb_fs_file_size( cwal_callback_args const * args,
                            cwal_value **rv ){
  char const * fn;
  fsl_size_t sz;
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if(!fn) return cb_toss(args, FSL_RC_MISUSE,
                         "Expecting non-empty string "
                         "argument.");
  sz = fsl_file_size(fn);
  if((fsl_size_t)-1 == sz){
    return cb_toss(args, FSL_RC_IO,
                   "Could not stat file: %s", fn);
  }
  *rv = (sz > (int64_t)CWAL_INT_T_MAX)
    ? cwal_new_double(args->engine, (cwal_double_t)sz)
    : cwal_new_integer(args->engine, (cwal_int_t)sz)
    ;
  return *rv ? 0 : CWAL_RC_OOM;
}

/**
   Script usage:

   const tm = Fossil.file.unlink(filename)
*/
static int cb_fs_file_unlink( cwal_callback_args const * args,
                              cwal_value **rv ){
  char const * fn;
  int rc;
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if(!fn || !*fn) return cb_toss(args, FSL_RC_MISUSE,
                                 "Expecting non-empty string "
                                 "argument.");
  rc = fsl_file_unlink( fn );
  if(!rc) *rv = args->self;
  return rc
    ? cb_toss(args, rc, "Got %s error while "
              "trying to remove file: %s",
              fsl_rc_cstr(rc), fn)
    : 0;
}

static int cb_fs_file_chdir( cwal_callback_args const * args,
                             cwal_value **rv ){
  char const * fn;
  int rc;
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if(!fn || !*fn) return cb_toss(args, FSL_RC_MISUSE,
                                 "Expecting non-empty string "
                                 "argument.");
  rc = fsl_chdir( fn );
  if(!rc) *rv = args->self;
  return rc
    ? cb_toss(args, rc, "Got %s error while "
              "trying to remove file: %s",
              fsl_rc_cstr(rc))
    : 0;
}

static int cb_fs_file_cwd( cwal_callback_args const * args,
                           cwal_value **rv ){
  int rc;
  enum { BufSize = 512 * 5 };
  fsl_size_t nLen = 0;
  char const slash = args->argc ? cwal_value_get_bool(args->argv[0]) : 0;
  char buf[BufSize] = {0};
  rc = fsl_getcwd(buf, BufSize, &nLen);
  if(rc) return cb_toss(args, rc, "Got error %d (%s) while "
                        "trying to getcwd()",
                        rc, fsl_rc_cstr(rc));
  if(slash && nLen<(fsl_size_t)BufSize){
    buf[nLen++] = '/';
  }
  *rv = cwal_new_string_value(args->engine, buf, (cwal_size_t)nLen);
  return *rv ? 0 : CWAL_RC_OOM;
}


/**
   Script usage:

   const tm = Fossil.file.mtime(filename)
*/
static int cb_fs_file_mtime( cwal_callback_args const * args,
                             cwal_value **rv ){
  char const * fn;
  fsl_time_t sz;
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if(!fn) return cb_toss(args, FSL_RC_MISUSE,
                         "Expecting non-empty string "
                         "argument.");
  sz = fsl_file_mtime(fn);
  if(-1 == sz){
    return cb_toss(args, FSL_RC_IO,
                   "Could not stat() file: %s", fn);
  }
  *rv = cwal_new_integer(args->engine, (cwal_int_t)sz);
  return *rv ? 0 : CWAL_RC_OOM;
}

/**
   Script usage:

   const bool = Fossil.file.isFile(filename)
*/
static int cb_fs_file_isfile( cwal_callback_args const * args,
                              cwal_value **rv ){
  char const * fn;
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if(!fn) return cb_toss(args, FSL_RC_MISUSE,
                         "Expecting non-empty string "
                         "argument.");
  *rv = fsl_is_file(fn)
    ? cwal_value_true()
    : cwal_value_false();
  return 0;
}

/**
   Script usage:

   const bool = Fossil.file.isDir(filename)
*/
static int cb_fs_file_isdir( cwal_callback_args const * args,
                             cwal_value **rv ){
  char const * fn;
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if(!fn) return cb_toss(args, FSL_RC_MISUSE,
                         "Expecting non-empty string "
                         "argument.");
  *rv = (fsl_dir_check(fn) > 0)
    ? cwal_value_true()
    : cwal_value_false();
  return 0;
}


/**
   Script usage:

   bool Fossil.file.access(filename [, bool mustBeADir=false)
*/
static int cb_fs_file_access( cwal_callback_args const * args,
                              cwal_value **rv ){
  char const * fn;
  char checkWrite;
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if(!fn) return cb_toss(args, FSL_RC_MISUSE,
                         "Expecting non-empty string "
                         "argument.");
  checkWrite = (args->argc>1)
    ? cwal_value_get_bool(args->argv[1])
    : 0;

  *rv = fsl_file_access( fn, checkWrite ? W_OK : F_OK )
    /*0==OK*/
    ? cwal_value_false()
    : cwal_value_true();
  return 0;
}


/**
   Streams a file's contents directly to the script engine's output
   channel, without (unless the file is small) reading the whole file
   into a buffer first.

   Script usage:

   Fossil.file.passthrough(filename)
*/
static int cb_fs_file_passthrough( cwal_callback_args const * args,
                                   cwal_value **rv ){
  char const * fn;
  cwal_size_t len;
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], &len)
    : NULL;
  if(!fn){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting non-empty string "
                   "argument.");
  }else if(0!=fsl_file_access(fn, 0)){
    return cb_toss(args, FSL_RC_NOT_FOUND,
                   "Cannot find file: %s", fn);
  }else{
    FILE * fi = fsl_fopen(fn, "r");
    int rc;
    if(!fi){
      rc = cb_toss(args, FSL_RC_IO,
                   "Could not open file for reading: %s",
                   fn);
    }else{
      rc = fsl_stream( fsl_input_f_FILE, fi,
                       fsl_output_f_cwal_output,
                       args->engine );
      fsl_fclose(fi);
    }
    if(!rc) *rv = args->self;
    return rc ? CWAL_RC_IO : 0;
  }
}

/**
   Script binding to fsl_stat(). Returns undefined if
   stat fails, else an object:
   
   {
   name: args->argv[0],
   mtime: unix epoch,
   ctime: unix epoch,
   size: in bytes,
   type: 'file', 'dir', 'link', or 'unknown'
   }

   TODO? Canonicalize the result name?
*/
static int cb_fs_file_stat( cwal_callback_args const * args,
                            cwal_value **rv ){
  char const * fn;
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if(!fn) return cb_toss(args, FSL_RC_MISUSE,
                         "Expecting non-empty string "
                         "argument.");
  else{
    fsl_fstat fst = fsl_fstat_empty;
    int rc;
    cwal_engine * e = args->engine;
    rc = fsl_stat( fn, &fst, 1 );
    if(rc){
      *rv = cwal_value_undefined();
      rc = 0;
    }else{
      char const * typeName;
      cwal_value * obj = cwal_new_object_value(e);
      switch(fst.type){
        case FSL_FSTAT_TYPE_DIR: typeName = "dir"; break;
        case FSL_FSTAT_TYPE_FILE: typeName = "file"; break;
        case FSL_FSTAT_TYPE_LINK: typeName = "link"; break;
        case FSL_FSTAT_TYPE_UNKNOWN:
        default:
          typeName = "unknown";
          break;
      }
      cwal_prop_set(obj, "type", 4, cwal_new_string_value(e, typeName,
                                                          cwal_strlen(typeName)));
      cwal_prop_set(obj, "name", 4, args->argv[0]);
      cwal_prop_set(obj, "size", 4, cwal_new_integer(e, (cwal_int_t)fst.size));
      cwal_prop_set(obj, "mtime", 5, cwal_new_integer(e, (cwal_int_t)fst.mtime));
      cwal_prop_set(obj, "ctime", 5, cwal_new_integer(e, (cwal_int_t)fst.ctime));
      *rv = obj;
      /* See how little code it is if we ignore malloc errors? */
    }
    return rc;
  }
}

static int fsl_add_file_funcs( cwal_engine * e, cwal_value * ns ){
  cwal_value * func;
  cwal_value * v;
  int rc;
  func = cwal_new_object_value(e);
  if(!func) return CWAL_RC_OOM;
  cwal_value_ref(func);
  rc = cwal_prop_set(ns, "file", 4, func);
  cwal_value_unref(func);
  if(rc){
    return rc;
  }
    
#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                                              \
  VCHECK;                                                      \
  cwal_value_ref(v);                                           \
  rc = cwal_prop_set( func, NAME, cwal_strlen(NAME), v );      \
  cwal_value_unref(v);                                         \
  if(rc) {goto end;}(void)0
#define FUNC(NAME,FP)                        \
  v = cwal_new_function_value( e, FP, 0, 0, 0 );    \
  SET(NAME)

  FUNC("canonicalName", cb_fs_file_canonical);
  FUNC("chdir", cb_fs_file_chdir);
  FUNC("currentDir", cb_fs_file_cwd);
  FUNC("dirPart", cb_fs_file_dirpart);
  FUNC("isAccessible", cb_fs_file_access);
  FUNC("isDir", cb_fs_file_isdir);
  FUNC("isFile", cb_fs_file_isfile);
  FUNC("mtime", cb_fs_file_mtime);
  FUNC("passthrough", cb_fs_file_passthrough);
  FUNC("size", cb_fs_file_size);
  FUNC("stat", cb_fs_file_stat);
  FUNC("unlink", cb_fs_file_unlink);
    
#undef SET
#undef FUNC
#undef VCHECK
    end:

    return rc;
}

static int cb_time_now( cwal_callback_args const * args,
                        cwal_value **rv ){
  char asJulian = 0;
  time_t now;
  asJulian = args->argc && cwal_value_get_bool(args->argv[0]);
  time(&now);
  *rv = asJulian
    ? cwal_new_double(args->engine,
                      fsl_unix_to_julian((fsl_time_t)now))
    : cwal_new_integer(args->engine,
                       (cwal_int_t)now)
    ;
  return *rv ? 0 : CWAL_RC_OOM;
}


static int cb_unix_to_julian( cwal_callback_args const * args,
                              cwal_value **rv ){
  cwal_int_t tm;
  if(!args->argc){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting one integer argument.");
  }
  tm = cwal_value_get_integer(args->argv[0]);
  *rv = cwal_new_double(args->engine,
                        fsl_unix_to_julian( (fsl_time_t)tm ));
  return *rv ? 0 : CWAL_RC_OOM;
}

static int cb_julian_to_unix( cwal_callback_args const * args,
                              cwal_value **rv ){
  cwal_double_t tm;
  if(!args->argc){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting one double argument.");
  }
  tm = cwal_value_get_double(args->argv[0]);
  *rv = cwal_new_integer(args->engine,
                         fsl_julian_to_unix( (double)tm ));
  return *rv ? 0 : CWAL_RC_OOM;
}

static int cb_julian_to_iso8601( cwal_callback_args const * args,
                                 cwal_value **rv ){
  cwal_double_t tm;
  char includeMs;
  char buf[24];
  if(!args->argc){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting one double argument.");
  }
  tm = cwal_value_get_double(args->argv[0]);
  includeMs = (args->argc>1) ? cwal_value_get_bool(args->argv[1]) : 0;
  if(fsl_julian_to_iso8601(tm, buf, includeMs)){
    *rv = cwal_new_string_value(args->engine, buf, includeMs ? 23 : 19);
    return *rv ? 0 : CWAL_RC_OOM;
  }else{
    return cb_toss(args, FSL_RC_MISUSE,
                   "Invalid Julian date value.");
  }
}

static int cb_julian_to_human( cwal_callback_args const * args,
                               cwal_value **rv ){
  cwal_double_t tm;
  char includeMs;
  char buf[24];
  if(!args->argc){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting one double argument.");
  }
  includeMs = (args->argc>1) ? cwal_value_get_bool(args->argv[1]) : 0;
  tm = cwal_value_get_double(args->argv[0]);
  if(fsl_julian_to_iso8601(tm, buf, includeMs)){
    assert('T' == buf[10]);
    buf[10] = ' ';
    *rv = cwal_new_string_value(args->engine, buf, includeMs ? 23 : 19);
    return *rv ? 0 : CWAL_RC_OOM;
  }else{
    return cb_toss(args, FSL_RC_MISUSE,
                   "Invalid Julian data value.");
  }
}

static int fsl_add_time_funcs( cwal_engine * e, cwal_value * ns ){
  cwal_value * func;
  cwal_value * v;
  int rc;
  func = cwal_new_object_value(e);
  if(!func) return CWAL_RC_OOM;
  cwal_value_ref(func);
  rc = cwal_prop_set(ns, "time", 4, func);
  cwal_value_unref(func);
  if(rc){
    return rc;
  }
    
#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                                              \
  VCHECK;                                                         \
  cwal_value_ref(v);                                           \
  rc = cwal_prop_set( func, NAME, cwal_strlen(NAME), v );   \
  cwal_value_unref(v);                                      \
  if(rc) {goto end;} (void)0
#define FUNC(NAME,FP)                   \
  v = cwal_new_function_value( e, FP, 0, 0, 0 );    \
  SET(NAME)

  FUNC("julianToHuman", cb_julian_to_human);
  FUNC("julianToISO8601", cb_julian_to_iso8601);
  FUNC("julianToUnix", cb_julian_to_unix);
  FUNC("now", cb_time_now);
  FUNC("unixToJulian", cb_unix_to_julian);
  FUNC("cpuTime", cb_run_timer_fetch);

#undef SET
#undef FUNC
#undef VCHECK
  end:
  return rc;
}

static int cb_fsl_rc_cstr( cwal_callback_args const * args,
                           cwal_value **rv ){
  if(!args->argc){
    return cb_toss(args,CWAL_RC_MISUSE,
                   "Expecting a single integer argument (FSL_RC_xxx value).");
  }else{
    cwal_int_t const i = cwal_value_get_integer(args->argv[0]);
    char const * msg = fsl_rc_cstr((int)i);
    *rv = msg
      ? cwal_new_xstring_value(args->engine, msg, cwal_strlen(msg))
      : cwal_value_undefined();
    return *rv ? 0 : CWAL_RC_OOM;
  }
}

static cwal_value * s2_fsl_ns( s2_engine * se ){
  int rc = 0;
  cwal_value * ns;
  cwal_value * v;
  char const * pKey = "Fossil";
  cwal_engine * e;
  ns = s2_prototype_stashed(se, pKey);
  if(ns) return ns;
  e = s2_engine_engine(se);
  assert(se && e);
  ns = cwal_new_object_value(e);
  if(!ns){
    rc = CWAL_RC_OOM;
    goto end;
  }
  rc = s2_prototype_stash( se, pKey, ns );
  if(rc) goto end;
#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                                       \
  VCHECK;                                               \
  cwal_value_ref(v);                                    \
  rc = cwal_prop_set( ns, NAME, cwal_strlen(NAME), v ); \
  cwal_value_unref(v);                                  \
  if(rc) {goto end; } (void)0
#define FUNC(NAME,FP)                           \
  v = cwal_new_function_value(e, FP, 0, 0, 0 ); \
  SET(NAME)
  
  v = cwal_new_string_value(e, "Fossil", 6);
  SET("__typename");

  FUNC("globMatches", cb_strglob);
  FUNC("isUuid", cb_is_uuid);
  FUNC("rcString", cb_fsl_rc_cstr);

  v = fsl_db_prototype(se);
  VCHECK;
  if( (rc = cwal_prop_set_with_flags( ns, "Db", 2, v,
                                      CWAL_VAR_F_CONST)) ){
    goto end;
  }

  v = fsl_cx_prototype(se);
  VCHECK;
  if( (rc = cwal_prop_set_with_flags( ns, "Context", 7, v,
                                      CWAL_VAR_F_CONST)) ){
    goto end;
  }

  if( (rc = fsl_add_delta_funcs(e, ns)) ) goto end;
  if( (rc = fsl_add_file_funcs(e, ns)) ) goto end;
  if( (rc = fsl_add_time_funcs(e, ns)) ) goto end;

#undef SET
#undef FUNC
#undef VCHECK
  end:
  return rc ? NULL : ns;
}

/**
    We copy fsl_lib_configurable.allocator as a base allocator.
*/
static fsl_allocator fslAllocOrig;

/**
    Proxies fslAllocOrig() and abort()s on OOM conditions.
 */
static void * fsl_realloc_f_failing(void * state, void * mem, fsl_size_t n){
  void * rv = fslAllocOrig.f(fslAllocOrig.state, mem, n);
  if(n && !rv){
    fprintf(stderr,"\nOUT OF MEMORY\n");
    fflush(stderr);
    s2_fatal(CWAL_RC_OOM,"Out of memory.");
  }
  return rv;
}

/**
    Replacement for fsl_lib_configurable.allocator which abort()s on OOM.
    Why? Because fossil(1) has shown how much that can simplify error
    checking in an allocates-often API.
 */
static const fsl_allocator fcli_allocator = {
fsl_realloc_f_failing,
NULL/*state*/
};


/**
   Gets called by s2sh's init phase to install our bindings.
*/
int s2_shell_extend(s2_engine * se, int argc, char const * const * argv){
  static char once = 0;
  int rc = 0;
  cwal_value * v /* temp value */;
  cwal_engine * e = s2_engine_engine(se)
    /* cwal_engine is used by the core language-agnostic script engine
       (cwal). s2_engine is a higher-level abstraction which uses
       cwal_engine to (A) provide a Value type system and (B) manage
       the lifetimes of memory allocated on behalf of the scripting
       language (s2). s2 has to take some part in managing the
       lifetimes, but it basically just sets everything up how cwal
       expects it to be, and lets cwal do the hard parts wrt memory
       management.
    */
    ;
  if(once){
    assert(!"libfossil/s2 bindings initialized more than once!");
    s2_fatal(CWAL_RC_MISUSE,
             "libfossil/s2 bindings initialized more than once!");
  }
  fslAllocOrig = fsl_lib_configurable.allocator;
  fsl_lib_configurable.allocator = fcli_allocator
    /* This MUST be done BEFORE the fsl API allocates
       ANY memory!
    */;

  if(0){
    /* force hash-based scope property storage for future scopes */
    int32_t featureFlags = cwal_engine_feature_flags(e, -1);
    featureFlags |= CWAL_FEATURE_SCOPE_STORAGE_HASH;
    cwal_engine_feature_flags(e, featureFlags);
  }

#define VCHECK if(!v && (rc = CWAL_RC_OOM)) goto end

  /* MARKER(("Initializing Fossil namespace...\n")); */

  fsl_timer_start(&RunTimer);

  v = s2_fsl_ns(se);
  VCHECK;
#if 1
  if( (rc = s2_define_ukwd(se, "Fossil", 6, v)) ) goto end;
#else
  cwal_scope * dest = cwal_scope_current_get(e)
    /* Where we want to install our functionality to. Note that we're
       not limited to installing in one specific place, but in practice
       that is typical, and the module loader's interface uses that
       approach. */;
  if( (rc = cwal_scope_chain_set_with_flags( dest, 0, "Fossil", 6,
                                             v, CWAL_VAR_F_CONST)) ){
    goto end;
  }
#endif

  {
    /* Extend the built-in Buffer prototype */
    cwal_value * bufProto = s2_prototype_buffer(se);
    assert(bufProto);
#define BFUNC(NAME,FP) cwal_prop_set( bufProto, NAME, cwal_strlen(NAME), \
                                      cwal_new_function_value(se->e, FP, 0, 0, 0))
    BFUNC("md5", cb_buffer_md5_self);
    BFUNC("sha1", cb_buffer_sha1_self);
    BFUNC("sha3", cb_buffer_sha3_self);
  }

#undef VCHECK
  end:
  return rc;    
}

#undef MARKER
#undef THIS_DB
#undef THIS_STMT
#undef THIS_F
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<








































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted bindings/s2/timeline.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#!/usr/bin/env f-s2sh
/**
   A very basic timeline application implemented in s2.

   Optional flags, passed after '--' on the CLI:

   -f|--files lists files changed by each commit.

   -R|--repo-db=DBNAME specifies a repository db (default=current checkout).
*/
assert Fossil.require;
Fossil.require(
['fsl/db/repoOrCheckout', // opens -R|--repo-db REPOFILE db or the current checkout
 'fsl/timeline/basic', // array of recent event table entries
 'cliargs' // flags passed after '--' are "script flags", and this module assists in using them
],
proc(db,tl,cli){
    const showFiles = cli.takeFlag('f', cli.takeFlag('files'));
    if(cli.hasFlags()){
        throw "Unexpected flags(s): "+{}.toJSONString.call(cli.flags);
    }else if(cli.hasNonFlags()){
        throw "Unexpected non-flag(s): "+cli.nonFlags.toJSONString();
    }
    const j2h = Fossil.time.julianToHuman;
    const showFilesChangedBy = proc(uuid){
        affirm 'string' === typename uuid;
        var gotOne = 0;
        db.each({
            sql:<<<EOSQL
            SELECT bf.uuid, filename.name fname, mlink.pid pid, mlink.fid fid, bf.size size
            FROM mlink, filename LEFT JOIN blob bf -- FILE blob
            ON bf.rid=mlink.fid LEFT JOIN blob bm -- MANIFEST/checkin blob
            ON bm.rid=mlink.mid
            WHERE bm.uuid = ?1
            AND filename.fnid=mlink.fnid AND bf.rid=mlink.fid
            AND bm.rid=mlink.mid
            ORDER BY filename.name EOSQL,
            bind: uuid,
            mode: 0,
            callback:proc(){
                if(1===rowNumber){
                    print("\t%1$-12s %2$-10s %3$10s Name".applyFormat(
                        "File", "UUID", "Size"
                    ));
                    gotOne=true;
                }
                print("\t%1$-10s   %2$.10s %3$10d %4$s".applyFormat(
                    (!this.fid ? "removed" : (!this.pid ? "added" : "modified")),
                    this.uuid|||"",
                    this.size|||0,
                    this.fname
                ));
            }});
        if(gotOne) print("") /*spacing */;
    };

    const lineFmt = "%1$-8s %2$.10s @ %3$s by %4$s\n\t%5$s";
    const typeMap = { // maps event.type labels to strings
        g: 'tag', w: 'wiki', ci: 'checkin',
        e: 'event', t: 'ticket'
    };
    tl.eachIndex(proc(v){
        print(lineFmt.applyFormat(typeMap[v.type]|||'???',
                                  v.uuid,
                                  j2h(v.mtime),
                                  v.user,
                                  v.comment));
        showFiles && showFilesChangedBy(v.uuid);
    });
});
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<












































































































































Deleted bindings/s2/unit/000-000-0empty.s2.

1
/* valgrind this to find the base s2 script overhead costs. */
<


Deleted bindings/s2/unit/000-000-abc.s2.

1
2
3
4
5
6
7
8
9
assert true;  // if it were always this easy
assert !false;
assert 1;
assert !0;
assert !undefined;
assert !null;
assert !'';
assert !(false && {another, error, skipped});
assert   22 === ('☺', __COLUMN) /* must count non-ASCII chars properly */;
<
<
<
<
<
<
<
<
<


















Deleted bindings/s2/unit/000-005-bitshift.s2.

1
2
3
4
5
6
7
8
9
10
11
12
assert 2 === 1<<1;
assert 64 === 256>>2;
assert 4 === 1<<1<<1;

assert 4 === 1<<1<<1 ||| 0;
assert 4 === 0 || 1<<1<<1 ||| 1;

assert 1 === 0b1;
assert 4 === 0b100;
assert 0xff === 0b11111111;
assert 0b0110 === (0b_11_10 & 0b_01_11);
assert 0b100 === 0b1 << 2;
<
<
<
<
<
<
<
<
<
<
<
<
























Deleted bindings/s2/unit/000-010-compare.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
assert true === true;
assert true == 1;
assert true !== 1;
assert true != 0;
assert !false;
assert !(false == 1);;
assert false != '0';
assert false == +'0';
assert false == '';
assert !!!'';
assert <<<X fooX === 'foo';

assert 42 === (1
       ? 2
         ? 4 + 38
         : -1
       : 0 /* '===' has higher prec than '?' */);
assert 42 === (true ? 1 ? 4 + 38 : -1 : 0);

assert 0 < 1;
assert 1 > 0;
assert !(0 > 1);
assert !(1 < 0);
assert 0 < 1 && 1 > 0;

assert !(3>7);
assert 1 ? 3<2+2 : 3>7;

// stack size error:  (1 ? 'hi' : (again && yet !wrong) )
// (is invalid syntax, but the error message could be better)
// hmmm:
assert 'hi' === (false ? nope : (1 ? 'hi' : (again && yet + !wrong) ));

assert 1 === 3.2 % 2.1 /* modulo is always integer */;

assert 1 < 1.1 /* this wasn't always the case in s2 */;
assert 1.1 > 1 /* and yet this was */;

/* Arguable (but consistent) result type behaviours: */
assert 18 === 8*2.3 /* integer result of double multiplication */;
assert 18.0 < 8.0*2.3 /* double result of double multiplication */;
assert 8.0*2.3 > 18 /* make sure this works both ways. */;
assert 18 < 8.0*2.3 /*b/c of the integer-to-double mode comparison at the cwal level*/;
assert !(8*2.3 > 18) /* integer math on the LHS of > op */;
assert 2 === 1/0.4 /* integer result of double division */;
assert 0 === 1/2;
assert 0 === 1/2.0;

assert 2 === 1 ? 2 ? 3 : 4 : 5;
assert 5 === 0 ? 1&&2 ? 3 : 4 : 5; 
assert 5 === 0 ? 1||2 ? 3 : 4 : 5; 
assert 4 === 1 ? 0 ? 3 : 4 : 5;
assert 3 === 0&&1 ? 2 : 3;
assert 2 === 1||0 ? 2 : 3;
assert 2 === 0||1 ? 2 : 3;
assert 3 === 0||0 ? 2 : 3;
assert 3 === false||null ? 2 : 3;

var y = "";
assert 1 === (y ||| 1);
assert "" === (0 ||| y);
var x;
assert 1 === (x ?: 1);
x = false;
assert false === (x ?: 1);
assert 0 === (0 ?: 1);
x = undefined;
assert 1 === (x ?: 1);

assert false === (false ?: throw 1) /* short-circuits the RHS */;
assert 1 === catch {undefined ?: throw 1}.message /* doesn't short-circuit the RHS */;


<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


















































































































































Deleted bindings/s2/unit/001-010-typename.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
assert 'string' === typeinfo(name '');
assert 'integer' === typeinfo(name 3 * 8);
assert 'double' === typeinfo(name 3.1);
assert 'function' === typeinfo(name print);
assert 'string' === typeinfo(name typeinfo(name 3));
assert 'undefined' === typeinfo(name undefined);
assert 'undefined' === typeinfo(name $omeUndefined$ymbol);
assert 'object' === typeinfo(name s2.('prototype'));
assert 'object' === typeinfo(name s2.'prototype');
assert 'object' === typeinfo(name s2.prototype);
assert 'object' === typeinfo(name s2['pro'+'totype']);
assert 'object' === typeinfo(name s2[('p'+('r'+('o')))+('totype')]);

assert 'integer' === typeinfo(name 8*2.3);
assert 18 === 8*2.3 /* LHS type determines result type,
                       but double on the RHS is honored
                       for multiplication/division purposes
                       and the result gets truncated to an int.
                    */;
assert 'double' === typeinfo(name 8.0*2.3);
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<








































Deleted bindings/s2/unit/001-011-nameof.s2.

1
2
3
var x;
assert nameof x === 'x';
assert 'x' === nameof x;
<
<
<






Deleted bindings/s2/unit/002-000-eval.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
assert undefined === eval -> {};
assert undefined === eval -> '';

assert 3 === eval {1 * 3};
assert 3 === eval -> {1 * 3};
assert '3 + 1' === eval "3 + 1";

assert 4 === eval -> "3 + 1";
assert eval {2+2} === eval -> "3 + 1";

assert '3  * 2+1; 0' === eval => {  3  * 2+1; 0  };
assert '3  * 2+1' === eval =>   3  * 2+1  ;
assert 7 === eval-> eval =>3*2+1;
assert 2 === eval -> eval { 1; 2 };
assert undefined === eval -> eval {};
assert undefined === eval -> eval { 1; 2;; };
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
































Deleted bindings/s2/unit/002-050-catch.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
const oldStackTrace = pragma(exception-stacktrace true);
assert undefined === catch {1+3};
var ex;
//ex = catch -> ",";
ex = catch -> {","};
//ex = catch{,}
//return ex;

assert typeinfo(isexception ex);
assert typeinfo(isstring ex.script);
assert typeinfo(isinteger ex.line);
assert typeinfo(isinteger ex.column);
assert typeinfo(isinteger ex.code);
//print(typename ex,':',ex);

assert 'CWAL_RC_TYPE' === catch{var x; x.foo}.codeString();
assert 'CWAL_RC_NOT_FOUND' === catch{unknownVar}.codeString();

/*
  dupe vars and var names which collide with a keyword currently
  throw, rather than being triggered like syntax errors. The reason
  is so that they are catchable. That is arguable behaviour but the
  current unit test system can't (without additional sub-scripts)
  'catch' a syntax error to test these...
*/
assert 'CWAL_RC_ALREADY_EXISTS' === catch{ var dupe, dupe }.codeString();
assert 'CWAL_RC_ALREADY_EXISTS' === catch{ var for /*<== any keyword*/ }.codeString();

assert 'CWAL_RC_TYPE' === catch{s2.import('.')/*cannot eval a directory*/}
    .codeString();

scope {
    var ex = exception("hi");
    assert 'CWAL_RC_EXCEPTION' === ex.codeString();
    assert 'hi' === ex.message;

    ex = exception(1,2);
    assert 1 === ex.code;
    assert 2 === ex.message;

    ex = proc(){
        return exception(1,2);
    }();
    assert 1 === ex.code;
    assert 2 === ex.message;
    assert typeinfo(isarray ex.stackTrace);

    const obj = {
        prototype: exception(1,2)
    };
    assert obj inherits ex.prototype;
    assert 1 === obj.code;
    assert 2 === obj.message;
    assert typeinfo(isobject obj);

    const e2 = catch{throw obj};
    assert e2 === obj && "obj is-a exception, so it is thrown as-is.";
}

assert undefined === (1 ? catch 0 : 0)
/* must not syntax error (fixed 20160205). Formerly the implicit scope
   pushed by 'catch' (resp. the 'eval' family of keywords) was not
   respecting the current ternary level (it was clearing it, on
   purpose, because that's what we want in most cases), causing a
   syntax error in such constructs. It now retains the ternary level
   if the 'eval' operand is not a block expression. */;

assert 0 === (1 ? catch {0:} : 0).message.indexOf("Unexpected ':'")
/* But _this_ use of ':', without a '?' in the same (explicit) scope,
   is not permitted. */;

ex = catch [1,2,3].get(-1);
assert typeinfo(isexception ex);
assert 'CWAL_RC_RANGE' === ex.codeString();

scope {
    const ex = catch proc() { proc() { proc() {throw 1}() }() }();
    assert typeinfo(isexception ex);
    const st = ex.stackTrace;
    assert st.length() >= 3 /*(may vary in amalgamated tests)*/;
    assert 47 === ex.column;
    assert 55 === st.0.column;
    assert 59 === st.1.column;
    assert 63 === st.2.column;
}

var line;
ex = catch{
    (line=__LINE+1), var x = {a:1 /*intentionally missing comma*/
     b:1}/* ACHTUNG: 'b' must be at column 5 or adjust the assertion below */
};
//print(__FLC,line, ex);
assert ex;
assert ex.column ===  5
  /* at one point it was reporting the line/column of the opening '{'
     for the object literal containing the syntax error. */;
assert ex.line === line;

/*
  On 20171130 it was accidentally discovered that tokens with token
  type IDs in the range (1..127) were, in effect, being treated as
  string literals by the eval engine. They now trigger a syntax error.
*/
assert 'CWAL_SCR_SYNTAX' === catch{/*<== a literal \r*/}.codeString();
assert 'CWAL_SCR_SYNTAX' === catch{``}.codeString();

pragma(exception-stacktrace oldStackTrace);
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






















































































































































































































Deleted bindings/s2/unit/003-000-string-ops.s2.

1
2
3
4
5
6
7
8
9
assert 'ab' === 'a'+"b";
assert 'a123' === 'a' + 123;
assert 123 === 123 + 'a';
assert '123' === '1' + 2 + 3;
assert 123 === +'1' + 122;
assert 'def' === 'd'+<<<X e X+"f";
assert 1 === 'a'.#;
assert '☺☺'.# === 2;
assert catch {'a'.# = 1 /*cannot assign*/};
<
<
<
<
<
<
<
<
<


















Deleted bindings/s2/unit/005-000-var-decl.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
assert 11 === var d, z, a=3, b = a,
  c = 1,
  e,
  g = 1+7+3;
assert 3 === const f = 3;

assert 3===a;
assert a===b;
assert a!=c;
assert 1===c;
assert undefined===d;
assert d===e;
assert 3 === f;

//const fail

// must syntax err:
//var w, y,, x
 // must throw (var already defined):
//var a

assert 8 === scope {var a = 7; assert 10 === a + b; a} + 1;

assert scope{11/*NOT a built-in value*/} === scope scope {
  scope {
    assert 3 === a;
      var u = 1+2+3-1+6;
  };
};

assert 'undefined' === typeinfo(name u);

scope {
    var a2 = 'aaa', b2 = 'aaa', c2 = 'aaa',
        d2 = ('aaa','aaa','aaa','aaa','aaa');
    // just checking the interning stats
    assert typeinfo(isstring a2);
    assert typeinfo(isstring b2);
    assert typeinfo(isstring c2);
    assert undefined === unset a2, b2;
    assert 'undefined' === typeinfo(name a2);
    assert 'undefined' === typeinfo(name b2);
    assert typeinfo(isstring c2);
  ;;
};

var zz = scope {
  var z = "the end";
  //var z; // must throw (z already defined)
  //"abc";
};

//assert 'the end' === zz;
//zz;

scope {
    /**
       2020-02-18: the := operator assigns as const in var decls and
       function parameter default values.
    */
    var a = 1, b := 2, c = 3;
    assert c===(a = c) /* non-const */;
    assert 'CWAL_RC_CONST_VIOLATION' === catch{b = a}.codeString();
    assert b === (c = b) /* non-const */;
    const d := 1 /* same as (const d = 1), but := is permitted for
                    consistency with var. */;
    assert 'CWAL_RC_CONST_VIOLATION' === catch{d = b}.codeString();
    var ex = catch { var a := , b };
    assert 'CWAL_SCR_SYNTAX' === ex.codeString();
    assert ex.message.indexOf(':=') > 0;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<














































































































































Deleted bindings/s2/unit/010-000-assign.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
print.test = print;
var top = 3;
scope{
  assert true /* Basic identifier assignments... */;
  var a = 3, b;
  b = a;
  assert b === a;
  //assert 4 !== c = 4 // must throw for 'c';

  var c;
  c = a = b = a - 2;
  assert 1 === c;
  assert 1 === a;
  assert 1 === b;
  top = 7;
};

assert top === 7;

scope {
  assert true /* Basic property assignments... */;
  var other = print.prototype;
  print.x = 7;
  other.test = 'test';
  assert 7 === print.x;
  print.test[other.test]['y'] = -1;
  assert -1 === print.y;
  print.y = var a = 1;
  assert 1 === print.y;
  assert 1 === a;
  print.y = a = -1;
  assert -1 === print.y;
  assert -1 === a;
};

scope {
  assert true /* Making sure 'this' is respected properly... */;
  var a, other = print.prototype;
  print.x = 0;
  //other.('z') = print.x = a = -1;
  other['z'] = print.x = a = -1;
  assert other.z === -1;
  assert print.x === -1;
  assert a === -1;
  print.x = 1;
  assert other.z === -1;
  assert print.x === 1;
  assert a === -1;
};

0 && scope {
  
  const c= -1;
  c = 1 /* Must fail (assign to const) */;
};

scope {
  var a, other = print.prototype; other.z = print.x = a = -1;
  assert -1 === a;
  assert other.z === a;
  assert print.x === a;
  assert other === print.prototype;
  assert print inherits other;
}

scope {
  var a = 1, b;
  assert (a -= 3) === -2;
  assert 3 === (b = a+=5);
  assert 6 === (a*=1+1);
  assert 1 === (b /= 2);
  assert 3 === (a>>=1);
  assert 2 === (b<<=1);
  a = 2; a*=3+2;
  assert 10===a;
  assert (a/=3*4%5) === 5;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


























































































































































Deleted bindings/s2/unit/010-010-properties.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

scope {
  var o = {}; 
  assert 'object' === typename o;
  o.x = o;
  o.x['x'].'z' = 1;
  assert 1 === o.x.'x'['x'].z;
  assert 1 === o.x['x'].('z');
  assert 1 === o.x['x'].('x').('z');
  assert 1 === o.x['x'].('x')['x'].('z');
  o.x = 1;
  o.x = o.x + 2;
  assert 3 === o.x;
  assert 6 === o.x * 2;
  assert 4 === 1 + o.('x');
  assert 2 === o['x'] * 3 % 7;
  var z = 'x';
  assert 3 === o[z];
  assert 9 === o.(z) * o[z];
}

scope {
  var o = {a:1};
  assert 2 === (o.a+=1);
  assert -2 === (o.a*=3*3-10);
  o.a = 2; 
  assert 10===(o.a*=3+2);
  assert 10 === o.a;
  assert 5 === (o['a'] /= 2);
  assert 20 === (o['a'] <<= 2);
  assert 20 === o.a;
}

scope {
  var o = {a:1}, o2 = {prototype:o};
  assert o2 inherits o;
  assert 1 === o2.a;
  o2.a = -1;
  assert 1 === o.a;
  assert -1 === o2.a;
  unset o2.a;
  assert 1 === o2.a;
  unset o2.a;
  assert 1 === o2.a /* o.a not cleared by unset */;
  o2.unset('a');
  assert 1 === o2.a /* o.a not cleared by o2.unset() */;
  o.clearProperties();
  assert undefined === o2.a;

  o.x = 1, o2.x = 1;
  assert 1 === o.x && 1 === o2.x;
  unset o.x, o2.x;
  assert undefined === o.x /* ensure unset handles commas properly */;
  assert undefined === o2.x /* ensure unset handles commas properly */;
}

scope {
    // Test 20190706 fix for bool lookup/property key bug...
    var o = {};
    assert undefined === o[true] /* must not resolve to a truthy-keyed
                                    property (e.g. a member method) */;
    o[true] = 3; // bool-typed property key
    assert undefined === o['x'] /*must not match a boolean true property key */;
    assert 3 === o[true] /* must match existing bool-type key */;
    assert undefined === o.true /* note that o.true is a STRING key, not bool */;
    assert undefined === o[false] /* just checking */;
    o[false] = 1;
    assert 1 === o[false] /* must match literal bool prop key */;
    assert undefined === o.false /* note that o.false is a STRING key, not bool */;
    assert undefined === o[0] /* must not match property key [false] */;
    o[0] = 2;
    unset o[false];
    assert undefined === o[false] /* must not match falsy property key [0] */;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




















































































































































Deleted bindings/s2/unit/010-050-incrdecr.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

if(1) {
  var a = 1;
  assert 1 === a++;
  assert 2 === a;
  assert 3 === ++a;
  assert 'CWAL_SCR_SYNTAX' === catch{++a = a}.codeString() /*invalid target for assignment*/;
  assert 3 === a /* b/c the exception is thrown before ++a happened */;
  assert 'CWAL_RC_NOT_FOUND' === catch{++unknownSymbol}.codeString() /*unknown symbol*/;
  assert 3 === (a = a++);
  assert 3 === a;
  assert 3 === a--;
  assert 2 === a;
  assert 1 === --a;
  assert 6 === --a + 2 * 3;
  assert 0 === a;
}

if(1) {
  var o = {a:1};
  var a = 1;
  assert 1 === o.a++;
  assert 2 === o.a;
  assert 3 === ++o.a;
  assert 'CWAL_SCR_SYNTAX' === catch{++o.a = o.a}.codeString() /*invalid target for assignment*/;
  assert 3 === o.a /* b/c the exception is thrown before ++o.a happened */;
  assert 3 === (o.a = o.a++);
  assert 3 === o.a;
  assert 3 === o.a--;
  assert 2 === o.a;
  assert 1 === --o['a'];
  assert 6 === --o.a + 2 * 3;
  assert 0 === o.a;
}

if(1){
    // Make sure incr/decr do not demote doubles to integers. (They used to.)
    var a = 0.0;
    assert 1.0 === ++a;
    assert 0.0 === --a;

    assert 0.0 === a++;
    assert 1.0 === a++;
    assert 2.0 === a;

    var o = {a:0.0};
    assert 1.0 === ++o.a;
    assert 0.0 === --o.a;
    assert 0.0 === o.a++;
    assert 1.0 === o.a++;
    assert 2.0 === o.a;

    // Make sure booleans are coerced to integers...
    a = false;
    assert 1 === ++a;
    a = true;
    assert true === a++ /* Interesting... but is it wrong? */;
    assert 2 === a;

    assert 1 === +true;
    assert 0 === +false;
    assert -1 === -true;
    assert 0 === -false;

}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


































































































































Deleted bindings/s2/unit/015-000-interceptors.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
if(!s2.propertyInterceptor){
    /**
       property interceptor support will almost certainly never be
       enabled - the chances of unwanted side effects, especially
       within cwal's limited key/value property model are simply too
       high. Also, the performance hit it applies to the library is
       not something i'm willing to accept for a sugar-only feature
       like this one. It might be more feasible if cwal had a
       higher-level properties API, instead of simply key/value pairs,
       but that's too heavy-weight for cwal's design goals.
    */
    // print("s2.propertyInterceptor() not enabled.");
    /* No 'return' - it messes up one of the unit tests. */
}else{
    affirm typeinfo(isfunction const mkint = s2.propertyInterceptor);

    scope {
        const o = {
            a: 1
        };
        assert mkint === mkint(o, 'b', proc(){
            if(argv.length()){
                // setter...
                interceptee.a = argv.0;
                //throw "testing";
                return /* return val is ignored for setters! */;
            }
            // getter...
            return interceptee.a;
        });
        assert 1 === o.b;
        o.b = 12;
        //throw {o};
        assert 12 === o.a;
        ++o.a;
        ++o.b; /* well that wasn't supposed to work. */
        o.b++; /* well that wasn't supposed to work. */
        assert 15 === o.b;
        assert o.a === o.b;

        const x = {prototype:o, foo: 7};
        assert o.a === x.b;
        x.b *= 2;
        assert 30 === o.a;
        assert 30 === x.b;
        o.a *= 2;
        assert 60 === o.a;
    }

    scope {
        const pro = {foo: 7};
        const x = {prototype: pro};

        mkint(pro, 'y', proc(){
            affirm pro === interceptee;
            affirm x === this;
            //return this.y;
            //throw {x: typename this.y};
            // this.y is NOT supposed to be resolving
            // as a Function here, but should recurse.
            return this.y(@argv);
        });
        x.y2 = mkint(proc(){
            affirm this === interceptee;
            affirm x === this;
            return this.y;
        });
        mkint(x, 'r', proc(){
            affirm this === interceptee;
            affirm x === this;
            return this.r /* why is this access not triggering the
                             interceptor again? It's most certainly
                             not by design. */;
        });
        mkint(x, 'zz', proc(){
            //break; // error info here is not useful.
            affirm this === interceptee;
            affirm x === this;
            if(argv.length()){
                this.foo = argv.0;
                return;
            }
            return this.foo;
        });
        x.zzz = mkint(proc(){
            //break; // error info here is not useful.
            affirm this === interceptee;
            affirm x === this;
            if(argv.length()){
                this.zz = argv.0;
                return;
            }
            return this.zz;
        });
        assert 7 === x.zzz;
        var ex;
        //why not triggering the interceptor here!?!?!?
        //ex = catch{x.y}.codeString();
        //assert typeinfo(isexception ex);
        //assert 'CWAL_RC_CYCLES_DETECTED' === ex.codeString();
        ex = catch{x.y2};
        assert typeinfo(isexception ex);
        assert 'CWAL_RC_CYCLES_DETECTED' === ex.codeString();
        assert typeinfo(isfunction x.r)
        /* This assertion is unexpected/wrong: i'm expecting an exception. */;
        unset ex;
        var i = 0;
        foreach(x=>k,v) print(++i, __FLC, k, typeinfo(name v));
        assert !i /* foreach skips over interceptors */;

        x.zzz = 3;
        assert 3 === x.zz;
        assert 3 === x.foo;

        foreach(x=>k,v) print(++i, __FLC, k, typeinfo(name v));
        assert 1===i /* .foo prop */;

    }

    scope {
        const ar = [1,2,3];
        ar.L = mkint(proc(){
            const x = 0 ? this : interceptee;
            affirm typeinfo(isarray interceptee);
            if(argv.length()){
                // setter...
                //print(__FLC,"setting",x,".length(",argv.0,")");
                x.length(argv.0);
                //throw "testing";
                return/* return val is ignored for setters! */;
                
            }
            // getter...
            //print(__FLC,"getting",x,".length()");
            return x.length();
        });
        assert 3 === ar.L;
        ar.L = 5;
        assert 5 === ar.length();
        assert undefined === foreach(ar=>k,v){
            k === 'L' && break k;
        } /* foreach() (currently) explicitly skips over interceptors */;
        var obj = {prototype:ar, x:1};
        foreach(obj=>k,v){
            assert k !== 'L';
        };

        var newLen = 2;
        obj.L = newLen;
        assert obj.L === ar.length();
        assert 2 === ar.L;
        assert ar.L === obj.length();
        assert ar.L === obj.L;
        obj.L *= newLen;
        newLen *= newLen;
        assert newLen === ar.L;
        assert newLen === ar.length();
        assert 2 === ar.1;
        assert undefined === ar[ar.L-1];

    }

    scope {
        const str = "abcdef";
        str.prototype.L = mkint(proc(){
            affirm "".prototype === interceptee;
            affirm typeinfo(isstring this);
            if(argv.length()){
                // setter...
                throw "Strings are immutable - cannot set their length.";
                
            }
            // getter...
            return this.length();
        });
        assert 6 === str.L;
        assert catch str.L = 1;
        assert str[5] === str[str.L-1];
    }

}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










































































































































































































































































































































































Deleted bindings/s2/unit/020-000-strings.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
/* String method tests... */

scope{
    assert 'ab' === 'a'.concat('b');
    assert 'ABC' === 'a'.concat('b','c').toUpper();
    assert 42 === '0*'.byteAt(1);
    assert undefined === '0'.byteAt(3);
    assert '*' === '1*1'.charAt(1);
    assert '☺' === '1☺1'.charAt(1);
    assert '*' === '1*1'[1];
    assert '☺' === '1☺'[1];
    assert '☺' === '☺1'[0];
    assert undefined === '☺1'[2];

    assert '.'.isAscii();
    assert !'1☺1'.isAscii();
    assert '...'[0].isAscii();
    assert '1☺1'[0].isAscii();
    assert !'1☺1'[1].isAscii();

    var x = "hi! there! this string cannot not get interned (not simple/short enough)";
    var y = "".concat(x);
    assert x === y
    /* String.concat() optimization: "".concat(oneString) returns
       oneString. Unfortunately, from the script level, we can't
       _really_ be sure that we have the same string instance. Except
       possibly via this little cheat... */;
    assert typeinfo(refcount x) === typeinfo(refcount y);
    var z = [x,x,x]; // just grab a few refs to x
    assert typeinfo(refcount x) === typeinfo(refcount z.0);
    assert typeinfo(refcount x) === typeinfo(refcount y);
    y = "".concat("",y) /* bypasses optimization */;
    assert typeinfo(refcount x) === typeinfo(refcount z.0);
    assert typeinfo(refcount x) > typeinfo(refcount y);
    // WEIRD... the refcount on y is 1 higher on the first hit:
    // print(__FLC,typeinfo(refcount x),typeinfo(refcount y),typeinfo(refcount z));
    // it's almost like an argv isn't letting its ref go (but gets
    // swept up by the next call), but i just checked and argv is
    // ref/unref'd properly.  Hmmm. Aha: it's the pending result value
    // in the stack (s2_eval_ptoker(), actually) at that point! Correct behaviour -
    // it gets cleaned up by s2_eval_ptoker() after the next expression (the first
    // call, were the refcount is +1 higher than we might (otherwise) expect).
    //print(__FLC,typeinfo(refcount x),typeinfo(refcount y),typeinfo(refcount z));
    //print(__FLC,typeinfo(refcount x),typeinfo(refcount y),typeinfo(refcount z));
}

scope {
    assert 'Z☺Z' === 'z☺z'.toUpper();
    assert 'z☺z' === 'Z☺Z'.toLower();

    var str = '↻Æ©';
    assert 5 === "©123©".length();
    assert 5 === "©123©".#;
    assert 7 === "©123©".lengthBytes();
    assert 3 === str.length();
    assert 3 === str.#;
    assert 7 === str.lengthBytes();
    assert '↻' === str.charAt(0);
    assert 'Æ' === str.charAt(1);
    assert '©' === str.charAt(2);
    assert '©' === str[2];
    assert undefined === str.charAt(3);
    assert undefined === str[3];
    assert 'Æ©' === str.substr(1);
    assert '↻Æ' === str.substr(0,2);

    
    str = 'こんにちは' /*no idea what this says (or not) - copied it from the net.
                        Google says it means "Hi there"*/;
    assert 15 === str.lengthBytes();
    assert 5 === str.length();
    assert 'こ' === str.charAt(0);
    assert 'ん' === str.charAt(1);
    assert 'に' === str.charAt(2);
    assert 'ち' === str.charAt(3);
    assert 'は' === str.charAt(4);
    assert 'んにち' === str.substr(1,3);
    assert 'にちは' === str.substr(2,-1);

    assert '' === str.substr(0,0);

    assert 'CWAL_RC_RANGE' === catch {"abc"[-1]}.codeString()
    /* negative (from-the-end) indexes are a potential TODO, currently disallowed. */;

    assert 'c' === 'abc'.charAt(2).charAt(0).charAt(0).charAt(0);
    assert 'c' === 'abc'[2][0][0]
    /* SOMETIMES a cwal-level assertion: lifetime issue. Triggering it
       depends on recycling settings, engine state, and... lemme
       guess... right: string interning again >:(. Somewhere we're
       missing a reference we need. This was "resolved" in
       s2_process_top() by adding its newly-pushed result value to the
       eval-holder list (if such a list is active).
    */;
    assert 'c' === 'abc'[2][0][0][0][0][0][0][0][0][0];
    
};


scope {
  /* The subtleties of split()... */
  const split = proc(s,sep){
    sep || (sep = ':');
    var ar = s.split(sep);
    var j = ar.join(sep);
    assert j === s;
    return ar;
  };

  scope {
    var str = "//aaa//b//c//xy//";
    var sep = '//';
    var ar = split(str,sep);
    assert ar.join(sep) === str;
    assert "" == ar.0;
    assert "" === ar.5;
    assert 6 === ar.length();
    assert 'aaa' === ar.1;
    assert 'xy' === ar.4;
  }

  scope {
    var str = "aaa/b/c/xy/";
    var sep = '/';
    var ar = split(str, sep);
    assert ar.join(sep) === str;
    assert 5 === ar.length();
    assert 'aaa' === ar.0;
    assert "" === ar.4;
 }

  scope {
    var str = "aaa©b©c©";
    var sep = '©';
    var ar = split(str, sep);
    assert ar.join(sep) === str;
    assert 4 === ar.length();
    assert 'aaa' === ar.0;
    assert "" === ar.3;
  }

  scope{
    var str = ":::";
    var sep = ':';
    var ar = split(str, ':');
    assert ar.join(sep) === str;
    assert 4 === ar.length();
    assert !ar.0;
    assert !ar.3;
  }

  scope {
    var str = ":";
    var sep = str;
    var ar = split(str, sep);
    assert ar.join(sep) === str;
    assert 2 === ar.length();
    assert !ar.0;
    assert !ar.1;
  }

  scope {
    var ar = split(":a:b:",':');
    assert 4 === ar.length();
    assert !ar.0;
    assert 'a' === ar.1;
    assert 'b' === ar.2;
    assert !ar.3;
  }

  scope {
    var ar = split("abc", '|');
    assert 1 === ar.length();
    assert 'abc' === ar.0;
  }

  scope {
    var ar = split("", '.');
    assert 1 === ar.length();
    assert "" === ar.0;
  }

  scope {
    var ar = split("a:::b", ':');
    assert 4 === ar.length();
    assert "a" === ar.0;
    assert !ar.1;
    assert !ar.2;
    assert "b" === ar.3;
  }

  scope {
      // The 'limit' semantics changed on 20160213 to match what JS does.
      // Note that we still don't accept split() with no args, like JS does.
      var ar = "a:b:c".split(':',2);
      assert 2 === ar.length();
      assert 'b' === ar.1;

      ar = 'a:b:c'.split('/');
      assert 1 === ar.length();
      assert 'a:b:c' === ar.0;
  }

  scope {
      /* "Empty split". String interning saves us scads of memory here
         when the input string is large... */
      var ar = "abc".split('');
      assert typeinfo(isarray ar);
      assert 3 === ar.length();
      assert 'c' === ar.2;

      ar = "abc".split('',2);
      assert 2 === ar.length();
      assert 'b' === ar.1;
  }

} /* end split() */

scope {
  //assert "3.00.1" === "3.00.1" /* just making sure */;
  //print("3.00.1", "3.0"+0.1);
  assert "3.00.1" === "3.0"+0.1; // string on the left
  assert 3.7 === 0.7 + "3"; // string on the right
  assert 3.74 === 0.7 + "3" + "0.04";
  assert 3.1 === 3.1 + "abc"; // "abc"==0
  assert 5 === +"5";
  assert -5 === -"5";
  assert -5 === +"-5";
  assert 1.2 === -"-1.2"; // but beware of precision changes on such conversions!
}

scope {
  const sp = "".prototype;
  sp.firstChar = proc(asInteger=false){
    return this.charAt(0, asInteger);
  };
  assert "a" === "abc".firstChar();
  //unset "".prototype.firstChar; // hmmm - unset cannot deal with this.
  unset sp.firstChar;
}

scope {
    var a = "a";
    a += "b";
    assert 'ab' === a;
}

scope {
    /* String.replace() tests... */
    proc x(haystack,needle,replace/*,limitAndOrExpect*/){
        const expect = argv.4 ||| argv.3,
           limit = argv.4 ? argv.3 : 0;
        const v = haystack.replace(needle, replace, limit);
        (expect === v)
            && (assert 1 /*String.replace() check passed*/)
            && return x;
        throw "Mismatch: expecting ["+expect+"] but got: ["+v.toString()+"]";
    }
    ("abc", 'b', 'B', 'aBc')
    ('abcabc', 'b', 'BB', 1, 'aBBcabc')
    ('ab©ab©ab©', 'b', 'BB', 2, 'aBB©aBB©ab©')
    ('(C)ab(C)', '(C)', '©', '©ab©')
    ('abc', 'x', 'y', 'abc')
    ('abc', 'abc', '', '')
    ('a\r\nc\r\n', '\r\n', '\n', 'a\nc\n')
    ;;
}

scope {
    assert 2 === 'abcd'.indexOf('c');
    assert 0 > 'abcd'.indexOf('e');
    assert 2 === 'ab©'.indexOf('©');
    assert 3 === 'ab©c'.indexOf('c');
    assert 3 === 'ab©cはd'.indexOf('cはd');

    // 20190713 bugfix: the indexOf() of same-length strings
    // was just plain broken due to an "optimization" which
    // backfired.
    assert 'a'.indexOf('b') < 0;
    assert 'cab'.indexOf('cab') === 0;
}

scope {

    /* 20191220: an invalid \Uxxxxxxxx value now triggers a syntax
       error rather than being immediately fatal with no error
       location info. Because it's a syntax error, and not an exception,
       it can only be catch'd if it comes from inside/through a block.
       i.e. (catch '\U00E0A080') will not convert it to an exception, but
       the following will... */       
    const ex = catch {
        '\U00E0A080'
    };
    assert ex;
    assert ex.message.indexOf('Uxx')>0;
    assert 'CWAL_SCR_SYNTAX' === ex.codeString();
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
















































































































































































































































































































































































































































































































































































































Deleted bindings/s2/unit/020-050-buffer.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336

const Buffer = s2.Buffer.new;

scope {

  var b = Buffer();
  assert b inherits s2.Buffer;
  assert !b.capacity();
  assert !b.length();
  b.length(10);
  assert 10 === b.length();
  assert b.# === b.length();
  assert b.capacity() >= b.length();
}


scope {
    const sz = 20, b = Buffer(sz);
    assert !b.#;
    assert b.isEmpty();
    assert b.capacity() >= sz;
    b.append('a');
    assert !b.isEmpty();
    assert 1 === b.#;
    b.appendf('%1$d', 1);
    assert 2 === b.#;

    b.fill(42);
    assert '**' === b.toString() /* ensure that fill() does not change the length() */;

    b.reset();
    assert 0 === b.#;
    b.fill('!');
    assert undefined === b.byteAt(0) /* ensure that fill() does not change the length() */;

    b.length(sz).fill( '$' );
    assert 36 === b.byteAt( 0 );
    assert 36 === b.byteAt( sz-1 );
    assert undefined === b.byteAt( sz );

    b.fill( '!', 1, 18 );
    assert 36 === b.byteAt( 0 );
    assert 36 === b.byteAt( 19 );
    assert 33 === b.byteAt( 1 );
    assert 33 === b.byteAt( 18 );
    assert '$!' === b.toString( 0, 2 );
}

scope {
    const fmt = proc callee(fmt){
        return callee.buffer.reset( 0 ).appendf.apply( callee.buffer, argv ).toString();
    };
    fmt.buffer = Buffer(100);
    const check = proc(cmp){
        argv.shift();
        const s = fmt.apply(fmt,argv);
        (s===cmp) || throw ("Mismatch: <<<"+cmp+">>> !== <<<" +s+">>>");
        assert 1 /* count the above comparison as an assertion.
                    We use throw only to get more info out of it
                    if it fails*/;
    };
    const av = [ 42, "string formatter", "abcde",
                 19.17, true, false, -13.19];

    assert 'CWAL_RC_RANGE' === catch{fmt("%8$+f", 0)/*not enough args*/}.codeString();

    check( '%', '%%' );
    check( '%x%y', '%%x%%y' );

    check( 'hi, world', "%1$s", 'hi, world' );
    check('hi, world (           hi, world)',
          "%1$s (%1$20s)", 'hi, world');
    check('integer 42 042 02A 002a',
          "%1$y %1$d %1$03d %1$03X %1$04x", av.0);
    check('(        42) (42        )',
          "(%1$10d) (%1$-10d)", av.0);
    check('(       +42) (+42       )',
          "(%1$+10d) (%1$+-10d)", av.0);

    check( '00042', "%1$05d", av.0 );
    check( '42.00000', "%1$0.5f", av.0 );
    check('string formatter', "%2$s", 0, av.1);
    check('string', "%2$.6s", 0, av.1);
    check('string formatter and string formatter',
          "%2$s and %2$s", 0, av.1);
    check( '(abcde     ) (     abcde)',
           "(%3$-10s) (%3$10s)", 0, 0, av.2 );
    check( '(     abcde) (abcde     )',
           "(%3$10s) (%3$-10s)", 0, 0, av.2 );
    check( '(       abc) (abc       )',
           "(%3$10.3s) (%3$-10.3s)", 0, 0, av.2 );
    check( '(abc       ) (       abc)',
           "(%3$-10.3s) (%3$10.3s)", 0, 0, av.2 );
    check( '(abcd) (abcd    )',
           "(%3$.4s) (%3$-8.4s)", 0, 0, av.2 );
    check( '19.17 19.2 19.17 19.170',
           "%4$f %4$.1f %4$.2f %4$.3f", 0, 0, 0, av.3 );
    check( '+19.17 (19.170    ) (    19.170) (+00019.170)',
           "%4$+0f (%4$-10.3f) (%4$10.3f) (%4$+010.3f)", 0, 0, 0, av.3 );
    check( '1 true false null undefined',
           "%5$d %5$b %6$b %5$N %5$U", 0, 0, 0, 0, av.4, av.5 );
    check( '0.0', "%6$+f", 0, 0, 0, 0, 0, av.5 );
    check( '-13.19', "%7$+f", 0, 0, 0, 0, 0, 0, av.6 );

    check( '68692C20776F726C64',
           "%1$B", "hi, world" );
    check( '68692C20',
           "%1$.4B", "hi, world" );
    check( '                     68692C20776F726C64',
           "%1$30B", "hi, world" );

    check( '-1.0', '%1$.1f', -1.02 );
    check( '+1.0', '%1$+.1f', 1.02 );

    check( '-1', '%1$d', (-1) );
    check( '-1', '%1$+d', (-1) );
    check( '+1', '%1$+d', 1 );

    check( 'h', '%1$c', 'hi' );
    check( '     hhhhh', '%1$10.5c', 'hi' );
    check( 'hhhhh     ', '%1$-10.5c', 'hi' );
    check( '  ©', '%1$3c', '©' );
    check( '©©©', '%1$0.3c', '©' );
    check( '©©©', '%1$.3c', '©' );
    check( '©©©  ', '%1$-5.3c', '©' );
    check( '  ©©©', '%1$5.3c', '©' );
    check( 'A', '%1$c', 65 );
    check( 'AAAA', '%1$.4c', 65 );
    check( '10', '%1$o', 8 );
    check( '0010', '%1$04o', 8 );
    check( '  10', '%1$4o', 8 );
    assert '1 2 3' === "%1$d %2$d %3$d".applyFormat( 1,2,3 );
    check( '(NULL)', '%1$q', null );
    check( "h''i", '%1$q', "h'i" );
    check( "'h''i'", '%1$Q', "h'i" );
    check( 'NULL', '%1$Q', null );

    // check 0-precision characters...
    check( '', '%1$.0c', '*' );
    check( '', '%1$0.0c', '*' );
    check( '  ', '%1$2.0c', '*' ) /* arguable, but currently true */;

    // check urlencoding/decoding
    check( 'a%20b%26c', '%1$r', 'a b&c' );
    check( 'a b&c', '%1$R', 'a%20b%26c' );
    check( 'a%2zb', '%1$R', 'a%2zb' );

    assert catch {'%1$.1r'.applyFormat()}.message.indexOf('precision')>0;
    assert catch {'%1$1r'.applyFormat()}.message.indexOf('width')>0;
    assert catch {'%1$.1R'.applyFormat()}.message.indexOf('precision')>0;
    assert catch {'%1$1R'.applyFormat()}.message.indexOf('width')>0;
}

scope{
    var b = Buffer(100);
    var x = 0, y = 0;
    b << <<<EOF
    for(var i = 0; i < 5; ++i) ++x, --y; 
    1
    EOF;
    var rc = b.evalContents("foo");
    assert 1 === rc;
    assert 5 === x;
    assert -x === y;

    b.reset() << <<<EOF
    0;
    1;
    return {a:'a',b:'bbbb'};
    EOF;
  
    var name = 'bar';
    rc = b.evalContents(name);
    assert rc.a === 'a';
    assert rc.b === 'bbbb';

    b.reset() << <<<EOF
    0;
    1;
    throw 3;
    return {a:'a',b:'bbbb'};
    EOF;
  
    var ex = catch{
        rc = 1
        ? b.evalContents(name)
            : eval ->b;
        throw "NOT REACHED";
    };
    assert ex;
    //print(__FLC,'ex =',ex) /* a cycle (the exception obj!) in the stack trace!?!?!? Corruption??? */;
    // something here is triggering an assertion in cleanup!
    // Worked around: comments are in evalContents() impl for further consideration later.
    assert name === ex.script;
    assert 3 === ex.message;
    assert 3 === ex.line;
    assert 4 === ex.column;

    assert 3 === "a+b".evalContents({a:1,b:2});
    assert 'xyz' === "__FILE".evalContents('xyz');
    assert 'xyz9' === "__FILE+a".evalContents({a:9},'xyz');
    assert 'xyz8' === "__FILE+a".evalContents('xyz',{a:8});
    assert 'test.x' === catch {"A_+B_".evalContents('test.x',{a:1,b:2})}.script;
}

scope {
    var b = Buffer() << "h©, ©orld";
    assert '©, ©' === b.substr(1,4);
    assert 'h©, ©orld' === b.substr(0,-1);
    assert '©orld' === b.substr(4);
    assert ', ©o' === b.substr(2,4);

    b.reset();
    assert '' === b.substr(1);
}

scope {
    var b = Buffer() << 1.00;
    assert "1.0" === b.toString() /* bugfix check: leave final trailing 0 after the dot. */;


    /* Testing Buffer.slice()... */
    b.reset() << "012345";
    proc x(expect,offset=0,count=-1){
        const v = b.slice(offset, count).toString();
        (expect === v) && (assert "slice() check passed") && return x;
        throw "Mismatch: expecting ["+expect+"] but got: ["+v.toString()+"]";
    }
    ("012345")
    ("012345",0)
    ("012345",0,-1)
    ("12345",1,-1)
    ("12345",1,50)
    ("5", 5, 1)
    ("5", 5, 10)
    ("", 5, 0)
    ("1234", 1, 4)
    ;

    /* Buffer.replace() tests... */
    b.reset() << "012345";
    const check = proc callee(needle,replace/*,limitAndOrExpect*/){
        const expect = argv.3 ||| argv.2,
           limit = argv.3 ? argv.2 : 0;
        const v = b.replace(needle, replace, limit).toString();
        (expect === v) && (assert 1 /*Buffer.replace() check passed*/) && return callee;
        throw "Mismatch: expecting ["+expect+"] but got: ["+v+"]";
    }
    ('1', '9', '092345')
    (57, 42, '0*2345')
    ('345', '**', 42, '0*2**')
    ('*', 'x', 1, '0x2**')
    ('0x2', '', '**')
    (42, 0, '\0\0')
    ;
    assert !b.isEmpty() /* technically speaking */;
    assert 2 === b.#;
    assert 0 === b.byteAt(1);
    var v = b.toString();
    assert 2 === v.#;
    assert '\0\0' === v;

    b.reset() << "012345";
    check('1', '©', '0©2345')
         ('345', '©©', '0©2©©')
    ;

    b.reset() << "a*b*c";
    assert b.replace(42,33).toString() === "a!b!c";
    b.reset() << "a*b*c";
    assert b.replace(42,33,1).toString() === "a!b*c";

    assert catch {b.replace("","x")}.codeString() === 'CWAL_RC_RANGE' /* needle must be >0 bytes */;
}

scope {
    /*
      Ensure that evalContents() behaves safely when the being-eval'd
      buffer's contents are modified during evaluation. i.e. the
      modifications are only temporary and get discarded when
      evalContents() is done.
    */
    const contents = "x = 2; b<<'abc'; assert 'abc'===b.takeString()";
    var x, b = Buffer() << contents;
    b.evalContents(__FLC);
    assert 2 === x;
    assert contents === b.takeString();
}


if(const BB = s2.Buffer.compression ? s2.Buffer : 0){
    assert 0 === catch {BB.isCompressed()}.message.indexOf("'this'");
    assert 0 === catch {BB.isCompressed(1)}.message.indexOf("Argument");
    assert 0 === catch {BB.uncompressedSize()}.message.indexOf("'this'");
    assert 0 === catch {BB.uncompressedSize(1)}.message.indexOf("Argument");
    const b = BB.readFile(__FILE);
    const len = b.#;
    assert len > 5000;
    assert !b.isCompressed();
    assert !b.isCompressed(b);
    assert 0 === catch {b.isCompressed(1)}.message.indexOf("Argument");
    assert !BB.isCompressed(b);
    assert undefined === b.uncompressedSize();
    assert undefined === BB.uncompressedSize(b);
    assert b === b.uncompress() /* must be a no-op */;
    assert b.# === len;

    assert b === b.compress();
    assert b.isCompressed();
    assert b.isCompressed(b);
    assert BB.isCompressed(b);
    const zlen = b.#;
    assert zlen < len;
    assert zlen > len/10;
    assert b === BB.compress(b) /* must be a no-op */;
    assert b.# === zlen;
    assert len === b.uncompressedSize();
    assert len === BB.uncompressedSize(b);

    assert b === BB.uncompress(b);
    assert !b.isCompressed();
    assert !b.isCompressed(b);
    assert b.# === len;
    assert undefined === b.uncompressedSize();
    assert undefined === BB.uncompressedSize(b);

}else{
    /* For the benefit of the -A flag (assert tracing). */
    var b = Buffer();
    assert !b.isCompressed();
    assert 'CWAL_RC_UNSUPPORTED' === catch {b.compress()}.codeString();
    assert 'CWAL_RC_UNSUPPORTED' === catch {b.uncompress()}.codeString();
    assert undefined === b.uncompressedSize();
    /* note that b.uncompressedSize() _would_ work if it was compressed,
       but that's hard to demonstrate here w/o compression support. */
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
































































































































































































































































































































































































































































































































































































































































































Deleted bindings/s2/unit/030-000-arrays.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
var a = [1,2,3], b = [];
assert 'array' === typename a;
assert 'array' === typename b;
assert 2 === a[1];

// unset array.index broken by other fixes:
assert undefined === unset a[1];
//a[1] = undefined;
assert undefined === a[1];

assert undefined === b[2];
a[3] = [4,5,[6,7,8]];
assert 'array' === typename a[3];
assert 'array' === typename a[3][2];
assert 8 === a[3][2][2];
assert 8 === a.3[2].2; // tokenizer sees a.3.2.2 as: a.(3.2).2
//assert 8 === a.3.2.2; // tokenizer sees 3.2 as a double
assert 8 === a.3[2][2];
a[] = 5*2-3;
assert 7 === a.4;
((a))[
        // note that space/comments don't count here
        ] = // nor on the RHS of a binary op
(2*5-2);
assert 8 === a.5;
assert 8 === a[4+1];
assert 9 === ++a[5];
assert 9 === a[5]++;
assert 10 === a[5];

a[3][2][] = 9;
assert 9 === a[3][2][3];

assert 'CWAL_SCR_SYNTAX'===catch{print[]/* must throw*/}.codeString();

// Must cause syntax errors:
assert catch{[1,2,3,,]}.message.indexOf('comma')>0;
assert catch{[1,2,,3]}.message.indexOf('comma')>0;
assert catch{[1,]}.message.indexOf('comma')>0;
assert catch{[,1]}.message.indexOf('comma')>0;
assert catch{[,]}.message.indexOf('comma')>0;
// Bugfix 20171111: disallow trailing semicolon in array value expressions:
assert catch{[1,2;]}.message.indexOf('semicolon')>0;
assert catch{[1;,2]}.message.indexOf('semicolon')>0;

0 && scope {
    var a = [];
    assert 1 === (a[]=1);
    assert 2 === a.length();
};

a /* propagate it out for lifetime checks */;
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<








































































































Deleted bindings/s2/unit/040-000-objects.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
/* Basic object literal tests. The prototype-supplied methods
   are tested in a separate script.
*/
const o = {
    a: 1, b: 2,
    c
:
3,
    4:'four',
    5: 5,
    array: [2,4,6],
    "removed": true
};

o.self = o;
o[((('d')))] = 42;
assert o === o.self;
assert 'array' === typename o.array;
assert 1 === o.a;
assert 2 === o['b'];
assert 3 === o.self['self'].self.c;
assert 'four' === o.4;
assert 5 === o[3+2];
//assert 5 === o.(3+2);
assert o.removed;
assert undefined === unset o.self.removed, o.removed /*does not fail on missing properties*/;
assert 'undefined' === typename o.removed;

//unset o.self /* cannot JSON-output cyclical objects */;
scope {
    const proto = { a:0 }, o = { prototype:proto, a:1 };
    assert o inherits proto;
    assert 1 === o.a;
    unset o.a;
    assert 0 === o.a;
    unset o.a;
    assert 0 === o.a;

    o.a = 1;
    ++o.a;
    assert 2 === o.a;
    o.a += -1;
    assert 1 === o.a;

    o.prototype = null /* 'null' or 'undefined' assignment removes a prototype... */;
    assert undefined === o.prototype /* ... which looks like this afterwards */;
    assert !(o inherits proto);
    assert o !inherits proto;
    assert 'CWAL_RC_TYPE' === catch{o.prototype = 1}.codeString();
}

scope {
    const g = {x:0};
    assert 1 === ++g.x;
    assert ++g.x < 3 /* must not error b/c of missing "this" via dot op */;
    assert g.x++ === 2 /* must not error b/c of missing "this" via dot op */;

    // Former BUG:
    g.x++ ? g.x-- : --g.x /* Must not error with: Unexpected LHS (X++ operation) for X?Y:Z operator. */;
    g /* trailing expr. needed to trigger the bug. */;
    ;;
}

assert catch{{a:1, b:2, }}.codeString() === 'CWAL_SCR_SYNTAX';
assert catch{{a:1,,  b:2, }}.codeString() === 'CWAL_SCR_SYNTAX';
assert catch{{,a:1,  b:2, }}.codeString() === 'CWAL_SCR_SYNTAX';
assert catch{{,}}.codeString() === 'CWAL_SCR_SYNTAX';
// Bugfix 20171111: disallow trailing semicolon in value-part expressions:
assert catch{{a:1;}}.message.indexOf('semicolon')>0;
assert catch{{a:1;,b:0}}.message.indexOf('semicolon')>0;

scope {
    /**
       Added 20171201: JS-like shortcut syntax:

       {a, b, c} ==> {a:a, b:b, c:c}
    */
    const a = 1, b = "hi", c = [1,2,3];
    const o = {a, x:4, b, c, y: 5};
    assert 1 === o.a;
    assert "hi" === o.b;
    assert c === o.c;
    assert 4 === o.x;
    assert 5 === o.y;
    assert catch{{x$x$x}}.codeString() === 'CWAL_RC_NOT_FOUND' /* unknown identifier */;
    assert catch{{"x"}}.codeString() === 'CWAL_SCR_SYNTAX' /* non-identifier */;
}

scope {
    /*
      Added 20191117: use an expression as an object literal key:

      const x = 'c';
      const o = {a:1, b: 2, [x]: 3}
    */ 
    const x = 'c';
    const o = {prototype: null, a:1, b: 2, [x]: 3};
    assert 3 === o[x];
    assert 3 === o.c;
    assert 1 === {
        prototype:null,
        [scope{
            for(;;) break "hi, "+"world";
        }]: 1
    }["hi, world"];

    assert 1 === {
        ["abc äbc"[4][0][0][0][0][0]]: 1
    }.ä;
    
    assert 0 === catch {{[x, break]: 1}}
        .message.indexOf("Unhandled 'break' in object literal.");
    assert 0 === catch {{[x, return]: 1}}
        .message.indexOf("Unhandled 'return' in object literal.");
    assert 0 === catch {{[x, continue]: 1}}
        .message.indexOf("Unhandled 'continue' in object literal.");
    ;;
}

assert 999 === scope {
    /*
      2016-02-03: testing fix:

      https://fossil.wanderinghorse.net/r/cwal/info/5041ab1deee33194

      If it's broken, this will crash if built in debug mode,
      triggering a cwal-level assertion, possibly a different one
      depending on the type of the scope result value's type.
    */
    {a: 999}.a
    /* 2020-02-20: The code that was testing has since disappeared: it
       seems that the related feature was no longer needed once cwal
       added scope push/pop APIs and cwal_scope_pop2(), both of which
       allowed that behaviour to be relegated to cwal. */
};

scope {
    /** 2020-02-06: dot-length operator (lhs.#) now works on non-list
        containers, resolving to the number of properties. */
    assert 2 === import.# /* path+doPathSearch properties */;
    assert 3 === {a:1, b:1, c:1}.#;
}

scope {
    /** 2020-02-18: {x:=y} sets x as a const property. */
    const p = {x:1};
    const o = {a:1, b:=2, c:3, prototype:=undefined};
    assert 2 === o.b;
    assert 'CWAL_RC_CONST_VIOLATION' === catch {o.b = 1}.codeString();
    assert p === (o.prototype = p)
      /* consting the prototype has no effect b/c we don't have a way
         to flag/enforce that constraint at the C level. */;
    assert 0 === o.clearProperties().#
           /* also removes const properties! Bug or feature? */;
    assert 1 === (o.b=1 /* constness was lost via clearProperties() */);
}

scope {
    /** 2020-02-18: x.y:=z sets x as a const property. */
    const o= {a:1};
    assert 2 === (o.a=2);
    assert 3 === (o.a:=3);
    assert 'CWAL_RC_CONST_VIOLATION' === catch {o.a = 1}.codeString();
    assert 'CWAL_RC_CONST_VIOLATION' === catch {o['a'] = 1}.codeString();
    assert 'CWAL_RC_CONST_VIOLATION' === catch {o.a := 1}.codeString();

    assert 'CWAL_SCR_SYNTAX' === catch {o := 1}.codeString()
      /* := op only works for property assignment. */;
    assert 1 === o.#;
    assert o === o.clearProperties();
    assert 0 === o.#;
}

scope {
    /** 2020-02-20: inline expansion of object properties into an
        object literal, similar to JS's {...otherObj}, which it calls
        "spread" syntax. We use the @ prefix because already use that
        for @array expansion, which is semantically very similar to
        what we use it for here.
    */
    var o = {a: 1, b: 2, c:3};
    var o2 = {@o};
    assert o.# === o2.#;
    foreach(o=>k,v) assert o2[k]==v;

    o2 = {@o, c:4};
    assert o.# === o2.#;
    assert 4 === o2.c;

    o2 = {a:4, @o, d:5};
    assert o.#+1 === o2.#;
    assert o.a === o2.a;
    assert 5 === o2.d;

    o2 = {d:5, @o};
    assert o.#+1 === o2.#;
    assert 5 === o2.d;

    o2 = {@{a:1, b:2}};
    assert 2 === o2.#;
    assert 1 === o2.a;
    assert 2 === o2.b;

    o2 = {@proc(){return {a:1,b:2}}(), c:3};
    assert 3 === o2.#;
    assert 1 === o2.a;
    assert 2 === o2.b;
    assert 3 === o2.c;
    
    var ex = catch{ {a:1, @, b:1} };
    assert 'CWAL_SCR_SYNTAX' === ex.codeString();
    assert ex.message.indexOf('empty expression') > 0;

    ex = catch{ {@3} };
    assert 'CWAL_RC_TYPE' === ex.codeString();
    assert ex.message.indexOf('container') > 0;

    assert 'CWAL_RC_CONST_VIOLATION' === catch o2 = {a:=1, @o}.codeString();
    o2 = {@o, a:=3};
    assert 3 === o2.a;
    assert 'CWAL_RC_CONST_VIOLATION' === catch {o2.a =1}.codeString();
    
    o = {
        // Special case: constructor in an object literal is
        // assigned as hidden/const, and hidden properties
        // are not iterated over but do count towards the
        // container.# operator...
        __new:proc(){}
    };
    assert 1 === o.#;
    o2 = {@o};
    assert 0 === o2.#;

    assert 'CWAL_RC_TYPE' === catch{
        /*
          The "problem" with enums is that they can use either object
          or hash storage internally, so we would need to
          differentiate between the two in the @ expansion.  We don't
          currently do that, but maybe someday will.

          ^^^^ that's outdated. enums now always use hashes.
        */
        {@enum {a,b,c}}
    }.codeString();
}

scope {
    /* 2021-06-24: ensure that vars declared in property access [] or ()
       are not leaked into the current scope. e.g.

       var o = {}; o[var x = 'hi'] = 1; assert 'hi'===x;
    */
    var o = {};
    o[var xx = 'a'];
    assert !typeinfo(isdeclared xx);
    o.(var xx = 'b');
    assert !typeinfo(isdeclared xx);
    /* But we want standalone (...) to run in the current scope. */
    1 && (var xx = 'y');
    assert 'y' === xx;
}

scope {
    /* 2021-07-09: Ensure that cwal_prop_key_can() prohibits certain
       property key types... */
    const o = {};
    assert 'CWAL_RC_TYPE' === catch{
        o[new s2.Buffer()] = 1;
    }.codeString();
    assert 'CWAL_RC_TYPE' === catch{
        o[[#]] = 1;
    }.codeString();
}

o /* propagate top-most o out for lifetime checks */;
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






































































































































































































































































































































































































































































































































































Deleted bindings/s2/unit/050-000-func-call.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161

scope {
    // make sure we can chain calls...
    const P = proc f(){return f};
    P.x = P;
    assert P === P('from P()');
    assert P === P.x('from P.x()');
    assert P === P.x['x']('from P.x["x"]()');

    // Demonstrate skip-mode's effect on function calls:
    false && P.x.y.z(foo);
    true || P.x.y.z(foo);

    assert P === (P.x)('from (P.x)()');

    var o = {
        p: P
    };
    assert P === o.p('from o.p()');

    if(s2.isCallable){
        assert s2.isCallable(s2.isCallable, false);
        assert s2.isCallable(s2.isCallable, true);
        assert s2.isCallable(s2.isCallable);
        o = {
            prototype: proc(){
                ++this.value;
                this.args = argv;
                return argv;
            },
            value: 0
        };
        assert 0 === o.value;
        assert undefined === o.args;
        assert o(1,2,3) === o.args;
        assert 1 === o.value;
        assert 3 === o.args.2;
        assert s2.isCallable(o) /* searches up through prototypes */;
        assert !s2.isCallable(o,false) /* does not search prototypes */;
        assert s2.isCallable(o,true) /* searches up through prototypes */;
    }
}

scope { // exploring "callable" objects...
    var obj = {
        foo: proc(){
            assert this === obj;
        }
    }.eachProperty(proc(k,v){
        //print(__FLC,'eachprop',k);
        v.importSymbols(nameof this);
    });

    // let's change up "this" a bit...
    var f = obj.foo;
    obj.foo();
    f();
    obj.bar = proc(name){
        //print(name,this);
        return this === eval -> name;
    };
    assert obj.bar(nameof obj);
    var x =obj.bar;
    assert x(nameof x);
}

scope {
    /* Prior to 20181015, completely empty functions were optimized
       away at call()-time. This led to side-effects in their
       parameter/argument lists not happening, e.g.: proc(){}(assert 0) did not
       fail. Make sure that's no longer the case...

       We now optimize away the call() part but still process the
       argument list, if any, so that side-effects can trigger.
    */
    var f = proc(){}, x = 3, y;
    f(y=x);
    assert x === y /* side-effects in argument list trigger */;
    assert (catch proc(){}(affirm 0)).message.indexOf('Affirmation') >= 0 /* affirmation must trigger */;

    /* Let's test some more complicated cases involving default
       parameter values which use call()-time imported symbols... */
    y = 0;
    f = proc callee(a=(y=Z), b=(z=callee.foo)){} using{Z:2};
    f.foo = 7;
    assert 0 === y;
    var z;
    f();
    assert 2 === y /* side effect of default param value using imported symbol */;
    assert 7 === z /* side effect of default param value using imported symbol */;
    y = 0;
    f.foo = 8;
    f(1);
    assert 0 === y /* skipped side effect of default param value */;
    assert 8 === z /* side effect of default param value using imported symbol */;
    z = 0;
    f(1,2);
    assert 0 === z /* skipped side effect of default param value */;

    z = 0;
    f = proc(a=(z=this.foo)){};
    f.foo = 7;
    f();
    assert 7 === z /* 'this' was set up before default parameter values were processed */;
}

scope {
    // Check for handling of errant continue/break...
    // Prior to 20181104 continue/break in call param lists
    // were handled incorrectly.

    const ar = [], f=proc(){};
    foreach(@[1,2,3]=>v) f(ar[] = v%2 ? v : continue);
    assert 2 === ar.#;
    assert 1===ar.0;
    assert 3===ar.1;

    ar.length(0);
    foreach(@[1,2,3]=>v) f(ar[] = v%2 ||| break);
    assert 1 === ar.#;
    assert 1===ar.0;

    var ex = catch foreach(@ar=>v) proc(){continue}();
    assert typeinfo(isexception ex);
    assert 0===ex.message.indexOf("Unhandled 'continue'");

    ex = catch foreach(@ar=>v) proc(){break}();
    assert typeinfo(isexception ex);
    assert 0===ex.message.indexOf("Unhandled 'break'");
}

scope {
    /**
       20190820: confirm fix of @-expansion bug when a newline preceeds
       the @expr in a function call. */
    const f = proc(){}, args = [1,2,3];
    f(1,2,3,
      @args);
    // ^^^ previously, that would fail with "'@' is not allowed here
}

scope {
    /**
       2020-02-18: var keyword now supports the := op to assign as
       const, but it is explicitly disabled for function parameters
       (which are handled by the same C-level code
       (s2_keyword_f_var_impl())) because of inconsistencies in
       parameters with and without default values. Namely, we can
       easily make the argument to proc(a:=2){...} const whether or
       not an argument is passed to the function call but we have no
       syntax to saying that it sould be const unless it has a default
       value. e.g. we could not make that same parameter const in
       proc(a){...}.
    */

    var f = proc(a:=1){} /* params are not parsed yet, so won't fail
                            here. */;
    var ex = catch f();
    assert 'CWAL_SCR_SYNTAX' === ex.codeString();
    assert ex.message.indexOf(':=') > 0;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


































































































































































































































































































































Deleted bindings/s2/unit/060-000-numbers.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
scope {
    /* Some sanity checks... */
    assert 0xffff === 0xf_f__f___f;
    assert 0b101 === 5;
    assert 0b101 === 0b_1__0___1;
    assert 0x13 === 0b_0001_0011;
    assert 123 === 1_2__3;
    assert 501 === 0o7_6__5;
}

scope {
  //print(0.INT_MIN, 0.INT_MAX);
  assert 0.INT_MIN < 0;
  assert 0.INT_MAX > 0;
  
  assert 0.INT_MIN-1 === 0.INT_MAX /* This (signed underflow/overflow) "should" be is platform-dependent: */;
  assert 0.INT_MAX+1 === 0.INT_MIN /* This (signed underflow/overflow) "should" be is platform-dependent: */;

  var i = 0;
  assert 'integer' === typename i;
  assert 0 === i.compare(0);
  assert i.compare(1) < 0;
  assert i.compare(-1) > 0;
  assert 0.0 === i.toDouble();
  assert 0 !== i.toDouble();
  assert "0" === i.toJSONString();
  assert "0" === i.toString();
  assert "1" === 1.toString();
  i = 42;
  assert '*' === i.toChar();
}

scope {
  var d = 0.0;
  assert 'double' === typename d;
  assert 0 === d.compare(0);
  assert d.compare(1) < 0;
  assert d.compare(-1) > 0;
  assert 0 === d.toInt();
  assert 0.0 !== d.toInt();
  assert "0.0" === d.toJSONString();
  assert "0.0" === d.toString();
  assert "1.0" === 1.0.toString();
  assert 1 === 0.5.ceil();
  assert 0 === (-0.5).ceil(); // parens needed: dot has higher precedence than unary -.
  //assert 0 === 0.ceil(); // INTEGER 0 doesn't have ceil()
  assert 0 === 0.0.ceil();
  assert -1 === (-1.0).ceil();
  assert -1 === (-1.01).ceil();
  assert 0 === (-0.999).ceil();
  assert -2 === (-2.2).ceil();
  assert 3 === 2.2.ceil();
  assert 2 === 2.0.ceil();

  assert 1 === 1.5.floor();
  assert 1 === 1.0.floor();
  assert 0 === 0.0.floor();
  assert 0 === 0.5.floor();
  assert -1 === (-0.5).floor();
  assert -2 === (-1.5).floor();
}

scope {
  1.0.prototype.twice = proc(){ return this * 2 };
  assert 6.2 === 3.1.twice();
  assert 8.0 === (2.0*2).twice();
  //unset 0.0.prototype.twice; // unset doesn't like this, so...
  var d = 0.0;
  unset d.prototype.twice, d;
}

scope { // number.parseInt/Double/Number()
    const pi = 0.parseInt, pf = 0.parseDouble, pn = 0.parseNumber;
    assert 1 === pi(1);
    assert 1 === pi('1');
    assert undefined === pn('1_');
    assert undefined === pi('1_');
    assert 1 === pi('1.0');
    assert -1 === pi('-1');
    assert -1 === pi('-1.2');
    assert undefined === pi('1-1');
    assert 1.0 === pf(1);
    assert 1.0 === pf('1');
    assert 1.0 === pf(true);
    assert 0.0 === pf(false);
    assert 0.0 === pf('-0');
    assert 1 === pn(1);
    assert 1 === pn('1');
    assert 1.0 === pn(1.0);
    assert 1.0 === pn('1.0');
    assert 0 === pn('-0');
    assert 0.0 === pn('  +   0.0');
    assert 56 === pn('0o70');
    assert 56 === pn('0o7_0');
    assert 56 === pn('0o_7__0');
    assert undefined === pn('0o1_')/*trailing non-digit*/;
    assert undefined === pn('0o18')/*trailing non-[octal-]digit*/;
    assert -1 === pn('-0o1');
    assert -1 === pn('-0x0001');
    assert undefined === pn('-0x0001.')/*trailing non-digit*/;
    assert undefined === pn(pn);
    assert pf('1') === pn('1.0');

    assert 'double' === typename pn("1.3") /* parseNumber() keeps the numeric type */;
    assert 'integer' === typename pn("1") /* parseNumber() keeps the numeric type */;
    assert 'integer' === typename pi("1.3") /* parseInt() reduces to an integer */;
}

scope { // number.nthPrime()
    assert 2 === 0.nthPrime(1);
    assert 7919 === 0.nthPrime(1000);
    assert 'CWAL_RC_RANGE' === catch {0.nthPrime(0)}.codeString();
    assert 'CWAL_RC_RANGE' === catch {0.nthPrime(1001)}.codeString();
    assert 'CWAL_RC_MISUSE' === catch {0.nthPrime()}.codeString();
}

scope { // toString()
    assert '1' === 1.toString();
    assert '1.0' === 1.0.toString();
    assert '1' === 1.0.toString('d');
    assert '000c' === 12.toString('04x');
    assert '000C' === 12.toString('04X');
    assert '0e' === 0b11_10.toString('02x');
    assert 'FBF' === 0x_f_B_f.toString('X');
    assert '765' === 0o7_6_5.toString('o');
    assert '765' === 0b111_110_101.toString('o');
}

scope {
    assert 0x_f_0 === 2_4__0;
    /* Interesting: we can't catch these errors because they trigger
       in the tokenizer while slurping the {...} blocks.  That means
       that catch cannot really know that it "could" safely convert
       these fatal syntax errors to non-fatal exceptions. */
    //assert 0 === catch {1_}.message.indexOf('Malformed');
    //assert catch {1_2.3}.message.indexOf('not legal') > 0;
    /* So... to test these we'll wrap them in strings and eval them in
       the 2nd-pass phase, as 2nd-pass eval knows that it can convert
       fatal syntax errors to non-fatal exceptions. Note that using
       eval=>{...} to capture them as strings cannot work here for the
       same timing reason. */
    assert 0 === catch -> {'1_'}.message.indexOf('Malformed numeric literal');
    assert 0 === catch -> {'1.2_'}.message.indexOf('Malformed numeric literal');
    assert 0 === catch -> {'0b1_'}.message.indexOf('Malformed binary');
    assert 0 === catch -> {'0o1_'}.message.indexOf('Malformed octal');
    assert 0 === catch -> {'0x1_'}.message.indexOf('Malformed hex');
    assert catch -> {'1_2.3'}.message.indexOf('not legal in floating-point') > 0;
}

if(0.INT_MAX > 0xFFFF_FFFF /* 64 bit */) {
    const largeI = 0xffff_ffff_ffff,
          largeD = largeI.toDouble();
    assert 0.parseNumber(largeD.toString()) === largeD;
    assert 0.parseNumber(largeI.toString()) === largeI;

    const largeD2 = 10.356 + 0xffffffffffff,
          d2Str = largeD2.toString();
    /* was "2.814749767107e14" prior to 20181127, but now
       it's "281474976710665.4" (or thereabouts). */
    assert d2Str.indexOf('e')<0 /* no scientific notation */;
    assert 0.parseNumber(d2Str) === largeD2;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




































































































































































































































































































































Deleted bindings/s2/unit/070-000-enum.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318


const countEach = proc callee(k,v){
    if(1===++callee.count){
        assert 'unique' === typeinfo(name v);
        assert 'string' === typeinfo(name k);
        assert this inherits enumProto;
    }
};

const enumProto = (enum {a}).prototype;

scope {
    assert 'enum' === typeinfo(name enum{a,b,c});
    assert 'CWAL_SCR_SYNTAX' === catch{enum {a,b,c, /* extra trailing comma */}}.codeString();
    assert 'CWAL_SCR_SYNTAX' === catch{enum {a,,c /* extra comma */}}.codeString();
    assert 'CWAL_SCR_SYNTAX' === catch{enum {a:,,c /* missing value after ':' */}}.codeString()
}

scope {
    const E = enum MyEnum {
        // This small enum will use an Object for storage.
        A, B, C
    };

    assert 'MyEnum' === typeinfo(name E);
    assert E inherits enumProto;
    assert 3 === E.#;
    assert E.A;
    assert undefined === E.A.prototype
        /* prototype pseudo-property is allowed but (currently) evals
	   to undefined */;
    assert E.hasEnumEntry(E.A);
    assert E.hasEnumEntry('A');
    assert !E.hasEnumEntry('a');
    assert 'C' === E[E.('C')];
    assert catch {E.x}.message.indexOf('Unknown')>=0;
    assert catch {E.A = 1}.message.indexOf('disallowed')>=0;
    var ar = E.getEnumKeys();
    assert 'array' === typeinfo(name ar);
    assert 3 === ar.length();
    assert ar.indexOf('C') >= 0;
    assert ar.indexOf('D') < 0;

    E.eachEnumEntry(countEach);
    assert 3 === countEach.count;
}

scope {
    const Big = enum {
        ☺,b,c,d,e,
        f,g,h,i,j,
        k,l,m,n,o,
        p,q,r,s,t,
        u,v,
        こんにちは
    };
    const bigLen = Big.#;
    assert 23 === bigLen;
    assert 'enum' === typeinfo(name Big);
    assert Big inherits enumProto;
    assert 'c' === Big[Big.c];
    assert '☺' === Big[Big.☺];
    assert 'こんにちは' === Big[Big.こんにちは];
    assert Big.こんにちは === Big.'こんにちは';
    assert Big[<<<X こんX + 'にちは'] === Big->'こんにちは';
    assert catch {Big.z}.message.indexOf('Unknown')>=0;
    assert catch {Big.b = 1}.message.indexOf('disallowed')>=0;

    var ar = Big.getEnumKeys();
    assert 'array' === typeinfo(name ar);
    assert bigLen === ar.length();
    assert ar.indexOf('☺') >= 0;
    assert ar.indexOf('B') < 0;

    countEach.count = 0;
    Big.eachEnumEntry(countEach);
    assert bigLen === countEach.count;
}

scope {
    var x = enum {a,b,c,d,e,f,g,h,i,j,k};
    assert 'unique' === typeinfo(name x.a);
    /**
       20191210: we may disallow enum prototype reassignment in the
       future, so don't rely on this feature. It's not a question of
       whether it makes sense to allow it, but whether or not it's a
       tolerable inconsistency vis-a-vis the disallowing of setting
       non-prototype properties. For relevant commentary, search for
       comments in s2.c near references to
       CWAL_CONTAINER_DISALLOW_PROP_SET. "The problem" is that if we
       allow it here, we always have to allow it on other "sealed"
       objects, such as the one which the s2out keyword resolves to.
    */
    x.prototype = {
        prototype: x.prototype,
        z: 3
    };
    assert 'enum' === typeinfo(name x);
    assert x.a;
    assert 3===x.z;
    assert 'CWAL_RC_DISALLOW_PROP_SET'
        === catch{x.z = 1}.codeString();
    // But...
    x.prototype.z = 4;
    assert 4===x.z /* modified in (mutable) prototype */;
    var ar = x.getEnumKeys();
    assert 'array' === typename ar;
    assert x.# === ar.length();
    assert 'CWAL_RC_NOT_FOUND'
      === catch{x['x']}.codeString();

}

scope {
   
    var x = enum {'a',b,c,d,e,f,g,h,i,j,k};
    assert 'unique' === typename x.a;
    assert 'CWAL_RC_DISALLOW_PROP_SET' === (catch x.z = 1).codeString();
    assert catch {x.z}.message.indexOf('Unknown')>=0;
    x.prototype = {
        prototype: x.prototype,
        z: 3
    };
    assert undefined === catch {x.z};
    assert 'enum' === typename x;
    assert x.a;
    assert 3===x.z;
    ++x.prototype.z;
    assert 4 === x.prototype.z;

    assert 'a' === x.a.value;
    assert 'a' === x::a;

    var e = enum { E, A: 'Aa', "B": 'abc', C: 13, OBJ: {x: 1, y:0}, D};
    assert 'enum' === typeinfo(name e);
    assert 'Aa' === e.A.value;
    assert 'abc' === e.B.value;
    assert 13 === e.C.value;
    assert 0 === e.OBJ.value.y;
    assert 1 === e.OBJ['value'].x;
    assert 1 === e.OBJ.('va'+'lue').x;
    assert 'CWAL_RC_TYPE' === catch (e.OBJ.invalid).codeString();
    assert e[e.A] === 'A';
    assert 'D' === e.D.value;
    assert 2 === e.A.value.length();
}

scope {
    const e = enum {
        a: {x:1, y:undefined},
        b: proc(){return 1},
        c,
        d
    };
    assert 4 === e.#;
    assert 1 === e.a.value.x;
    assert 1 === e::a.x;
    assert 1 === e::'a'.x;
    assert 1 === e.b.value();
    assert 1 === e::b();
    assert undefined === e.a.value.y++;
    assert e.a.value.x === e.a.value.y;
    assert 2 === ++e::a.y;
    assert 2 === e::a.y;
    assert 2 === e::(scope{while(true) break 'a'}).y
        /* reminder: parens needed b/c dot/dotdot treat all identifiers
           on the RHS equally, and does not expand keywords as keywords. */;
    assert e.c;
    assert 'c' === e::c;
    assert 'CWAL_RC_DISALLOW_PROP_SET' === (catch e.a = 1).codeString();
    assert 'CWAL_RC_NOT_FOUND' === catch{e.x}.codeString();
    assert 'CWAL_RC_NOT_FOUND' === catch {e::x}.codeString();
}

scope {
    scope {
        var e = enum {a:{x:1},b:{x:2}};
        e.a.value.b = e.b;
        e.b.value.a = e.a;
    }
    assert 1 /* must not crash during prior scope's cleanup (former
                cwal bug in cleanup handling of Unique-type values).*/;
    /* Must also properly upscope everything... */
    assert 200 === scope {
        var e = enum {a:{x:100},b:{x:200}};
        e::a.b = e.b;
        e::b.a = e.a; 
        assert e.a.value.b === e::a.b;
        assert e::a.b === e.b;
        e.a.value.b/*===e.b*/
    }.value.a/*===e.a*/.value.b.value.x;
    assert scope {
        enum {a:{x:100}}::a
    }.x === 100;
    assert enum {a:{x:-100}}::a.x === -100;
}

scope {
    const o2e = proc(obj, name) {
        affirm typeinfo(iscontainer obj);
        var b = new s2.Buffer(100).append("enum ", name ||| '', '{'),
            first = true;
        foreach(obj=>k){
            first ? first = false : b.append(", ");
            b.append(k , ": obj.",k);
        }
        return b.append("}").evalContents(__FLC);
    };
    var obj = {a:1, b:{x:2}};
    var e = o2e(obj, 'blah');
    assert 'blah' === typeinfo(name e);
    assert 2 === e.#;
    //print(__FLC, e);
    foreach(obj=>k, v) {
        assert e::(k) === v;
        assert e->(k).value === v;
        assert e.(k).value === v;
        assert v === ('e.'+k+'.value').evalContents(__FLC);
        assert v === ('e::'+k).evalContents(__FLC);
    }
    
    assert enum{a:enum{x:30}}::a::x + enum{b:30}::b === 60 /* lifetime(s) check */;
}

scope {
    const e = enum {
      f1: proc(){assert 'enum' === typeinfo(name E)},
      f2: proc(){assert 'unique' === typeinfo(name E.f1)}
    };
    const imports = {E:e};
    foreach(e=>k,v) v.value.importSymbols(imports);
    e::f1();
    e::f2();
    e::f1.z = e.f1
        /* will propagate e.f1 out of the scope. Formerly this crashed
           during scope cleanup due to a mis-assertion in the Unique-type's
           finalizer. Let's see what happens to it... */;
}

scope {
    /* 20191210: it's now possible (though probably not wise) to set
       the prototype property in an enum's body. Before this, that
       handling was not well-defined. */
    const x = {__typename: "X"};
    assert catch {enum {prototype:x}}.message.indexOf("at least one") > 0;
    var e = enum {prototype:x, a};
    assert 'X' === typeinfo(name e);
    assert e inherits x;
    assert typeinfo(isenum e);
    e = enum eee {prototype:x, b, c};
    assert 'eee' === typeinfo(name e);
    assert e inherits x;
    assert typeinfo(isenum e);

    e = enum hhh {prototype:x, b, c, d, e, f, g, h, i, j, k};
    assert 'hhh' === typeinfo(name e);
    assert e inherits x;
    assert e.prototype === x;
    assert typeinfo(isenum e);

    e = enum { prototype: null /* remove prototype */, a };
    assert catch {e.prototype}.message.indexOf('Unknown')===0;
    /**
       Interestingly, this formulation doesn't work:

       assert catch e.prototype.message.indexOf('Unknown')===0;

       Unexpected consecutive non-operators:
         #3006 (KeywordCatch) ==> #1011 (Identifier)

       At the ".message" part. Because... ???

       Hmmm.
    */

    ;;; /* <=== for vacuum testing */
}

scope {
    /* As of 2020-02-21, enums are always hashes, but the the
       restriction against using the hash search operator persists
       because it would otherwise behave exactly as the -> operator.
       typeinfo(ishash) still evals to false for enums.
    */
    const e = enum{a};
    assert typeinfo(isunique e.a);
    assert 'a' === e::a;
    assert e.a === e->'a';
    assert catch {e#'a'}.codeString()==='CWAL_RC_TYPE' /* hash op is not allowed on enums */;
    assert e::a === e.'a'.value;
    assert typeinfo(is-enum e);
    assert !typeinfo(is-hash e);
}

scope {
    /* Examples from the user docs... */
    const e = enum OptionalTypeName {
        a, // its own name (a string) as its value
        b: 2, // any value type is fine
        f: proc(){return this}
    };
    assert 3 === e.#;
    assert 'a' === e.a.value;
    assert 'a' === e::a; // equivalent
    assert 2 === e.b.value;
    assert 2 === e::b; // equivalent unless the entry.value is a Function call:
    assert e.f === e.f.value(); // e.f is bound as 'this'
    assert e::f === e::f(); // no 'this', so e.f.value is its own 'this'
    assert 'b' === e[e.b]; // get the string-form name of an entry
    assert e.a === e['a']; // note that e['a'] is functionally identical to e.a.
    assert 'OptionalTypeName' === typeinfo(name e);
    assert undefined === e->'c'; //-> op does not throw for unknown properties
    assert catch {e#'c'}.codeString()==='CWAL_RC_TYPE'; // hash op not allowed
    assert catch {e::c}.codeString()==='CWAL_RC_NOT_FOUND'; // unknown property
    assert catch {e.c}.codeString()==='CWAL_RC_NOT_FOUND'; // unknown property
    assert catch {e.a = 1}.codeString()==='CWAL_RC_DISALLOW_PROP_SET';
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




























































































































































































































































































































































































































































































































































































































































Deleted bindings/s2/unit/099-000-typeinfo.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/*
  typeinfo keyword tests...
*/

assert typeinfo(isbool true);
assert typeinfo(is-bool false);
assert !typeinfo(isbool 1);

assert typeinfo(isinteger 3);
assert !typeinfo(is-integer 3.3);
assert typeinfo(isdouble 3.3);
assert !typeinfo(is-double 3);
assert typeinfo(hasprototype 3);

scope {
    const b = s2.Buffer.new();
    assert typeinfo(isbuffer b);
    assert typeinfo(hasbuffer b);
    assert !typeinfo(is-buffer {prototype:b});
    assert typeinfo(has-buffer {prototype:b});
    assert !typeinfo(isbuffer s2.Buffer);
    assert !typeinfo(hasbuffer s2.Buffer);
    assert typeinfo(cannew s2.Buffer);
    assert typeinfo(can-new s2.Buffer);
    assert !typeinfo(isnewing s2.Buffer);
    assert !typeinfo(is-newing s2.Buffer);
    /* is-newing positive test is in the 'new' unit tests */
    assert !typeinfo(cannew 0);
    assert !typeinfo(can-new 0);
}

assert !typeinfo(isdeclared hasNative);
assert !typeinfo(islocal hasFunction);


const obj = {},
      ar = [],
      h = {#},
      hasHash = {prototype:h},
      hasArray = {prototype:ar},
      f = proc(){},
      hasFunction = {prototype:f},
      e = enum {a:undefined,b},
      ex = exception(0,0),
      hasEx = {prototype: ex},
      pf = new s2.PathFinder(),
      hasNative = {prototype:pf}
;

assert catch {
    obj = {a:1}
    /* 20160226: BUG: when hashes are used as scope storage, const
       violations are not caught! Not sure why - a cursory examination
       seems to imply that all const checks are in place.

       Can't reproduce it in the interactive shell!?!?

       Was caused by the automatic resizing of hashes losing their
       flags.
    */;
};

assert typeinfo(is-declared hasFunction);
assert typeinfo(is-local hasFunction);
scope{
    var x;
    assert !typeinfo(islocal hasFunction);
    assert typeinfo(islocal x);
}

assert typeinfo(hasprototype obj);
assert !typeinfo(has-prototype {prototype:null});

assert typeinfo(hasobject obj);
assert typeinfo(isobject obj);
assert typeinfo(has-object ar);
assert !typeinfo(is-object ar);
assert typeinfo(iscontainer ar);

assert typeinfo(hasobject 0);
assert !typeinfo(isobject 0);
assert !typeinfo(hasobject null);

assert typeinfo(isderefable obj);
assert typeinfo(is-derefable ar);
assert typeinfo(isderefable f);
assert typeinfo(iscontainer f);
assert typeinfo(isderefable 0);
assert !typeinfo(is-container 0);

assert !typeinfo(isderefable null);
assert !typeinfo(iscontainer null);
assert !typeinfo(isderefable undefined);
assert !typeinfo(isderefable true);
assert typeinfo(isderefable e.a);
assert !typeinfo(isderefable e.a.value);

assert typeinfo(isarray ar);
assert typeinfo(hasarray ar);
assert typeinfo(islist ar);
assert !typeinfo(is-array obj);
assert !typeinfo(has-array obj);
assert !typeinfo(isarray hasArray);
assert typeinfo(hasarray hasArray);
assert !typeinfo(islist hasArray);

assert typeinfo(iscallable f);
assert typeinfo(is-callable hasFunction);
assert typeinfo(isfunction f);
assert !typeinfo(is-function hasFunction);

assert typeinfo(isenum e);
assert typeinfo(is-enum e);
assert !typeinfo(isenum obj);
assert typeinfo(isunique e.a);
assert typeinfo(is-unique e.a);

assert !typeinfo(hashash obj);
assert !typeinfo(ishash obj);
assert !typeinfo(is-hash e)
 /* 2020-02-21: though enums are technically hashes, we
    report them here as non-hashes because not all hash
    operations work on enums and reporting them as such
    might lead script code to inadvertently send an enum
    down a hash-only code path. */;
assert !typeinfo(has-hash e);

assert typeinfo(has-hash h);
assert typeinfo(is-hash h);
assert typeinfo(hashash hasHash);
assert !typeinfo(ishash hasHash);

assert typeinfo(isexception ex);
assert typeinfo(hasexception hasEx);
assert !typeinfo(is-exception hasEx);
assert typeinfo(has-exception ex);


assert typeinfo(isinteger 1);
assert !typeinfo(is-integer '1');
assert typeinfo(isdouble 1.0);
assert !typeinfo(is-double 1);

assert typeinfo(isnumber 1);
assert !typeinfo(is-number true);
assert typeinfo(isnumeric true);
assert typeinfo(is-numeric '3');
assert !typeinfo(isnumeric '3x');
assert typeinfo(isnumeric '-3.3');
assert typeinfo(isnumeric '+0o7');
assert !typeinfo(isnumeric '0o8');

assert typeinfo(isnative pf);
assert typeinfo(hasnative pf);
assert !typeinfo(is-native obj);
assert !typeinfo(isnative hasNative);
assert typeinfo(has-native hasNative);
assert !typeinfo(hasnative obj);

assert typeinfo(isstring '');
assert !typeinfo(is-string 1);
assert typeinfo(isstring <<<X X);
assert typeinfo(isstring for(;;) break '1');

assert 'array' === typeinfo(name ar);

obj.x = 1;
assert typeinfo(mayiterateprops obj);
obj.eachProperty(proc(){
    assert typeinfo(mayiterateprops this);
    assert typeinfo(mayiterate this);
    assert !typeinfo(mayiteratelist this);
    assert typeinfo(isiteratingprops this);
    assert !typeinfo(isiteratinglist this);
    assert typeinfo(isiterating this);
    assert 'CWAL_RC_IS_VISITING' === catch {this.x=1}.codeString();
});
foreach(obj=>k){
    assert typeinfo(may-iterate-props obj);
    assert typeinfo(may-iterate obj);
    assert !typeinfo(may-iterate-list obj);
    assert typeinfo(is-iterating-props obj);
    assert !typeinfo(is-iterating-list obj);
    assert typeinfo(is-iterating obj);
}
assert !typeinfo(isiterating obj);
assert !typeinfo(isiteratingprops obj);
assert !typeinfo(isiteratinglist obj);

scope {
    var t0 = [#/* empty tuple is a built-in constant */],
        t2 = [#1,2];
    assert typeinfo(istuple t0);
    assert typeinfo(is-tuple t2);
    assert typeinfo(islist t0);
    assert typeinfo(islist t2);
}

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<












































































































































































































































































































































































































Deleted bindings/s2/unit/100-000-object-methods.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166

var o = {a:1, 2:'two', __typename:'Fred'};
scope {
  assert 'Fred' === typename o;
  assert o.hasOwnProperty('a');
  assert !o.hasOwnProperty('hasOwnProperty');
  assert o.prototype.hasOwnProperty('hasOwnProperty');
  assert o.hasOwnProperty(2);
  assert pragma(build-opt CWAL_OBASE_ISA_HASH)
        ? !o.hasOwnProperty('2') : o.hasOwnProperty('2');
  unset o.2;
  assert !o.hasOwnProperty(2);
  assert 0 != o.compare({});
  assert 0 === o.compare(print,print);
  assert 0 === o.compare(o);
  assert 0 === o.compare(o,o);
  assert 0 === o.compare(1,1);
  assert o.compare(1,-1) > 0;
  assert o.compare(-1,1) < 0;
}

scope {
  var k = o.propertyKeys();
  assert 'array' === typename k;
  assert 'string' === typename k.0;
  //print('o.propertyKeys() ==',k);
}


scope {
  o.clearProperties();
  assert 'object' === typename o;
  assert undefined === o.a;
}

scope {
  assert !o.hasOwnProperty('x');
  o.x = 1;
  assert 1 === o.x;
  assert o.hasOwnProperty('x');
  o.unset('x');
  assert undefined === o.x;
  assert !o.hasOwnProperty('x');
}

scope {
  assert o.mayIterate();
}

scope {
    o.a = 3, o.b = 7, o.c = 42.24, o.d = [1,2,3], o.1 = -1;
    const json = o.toJSONString(' ');
    const o2 = eval -> json;
    assert typename o === typename o2;
    assert o2.d[1] === o.d[1];
    if(!pragma(build-opt CWAL_OBASE_ISA_HASH)){
        /*only works if !CWAL_OBASE_ISA_HASH, else order is undefined*/
        assert o2.toJSONString(1) === json;
        const abc = o.toJSONString('abc');
        assert abc.indexOf('abc')>0;
        assert abc.toLower() === o2.toJSONString('ABC').toLower();
    }
}

scope {
  o.x = -1;
  assert -1 === o.get('x');
  o.set('x',1);
  assert 1 === o.get('x');
  o.set({Z: 'zzz'});
  assert 'zzz' === o.Z;
  assert true === o.unset('x','Z');
  assert false === o.unset('x','Z');
  assert undefined === o.Z;
  //o.compare(); // check that exception contains call-point line/col info
}

scope {
  o.s = o;
  var counter = 0;
  o.p = proc f(){
    counter = counter + 1;
    return f;
  };
  o.('p')(__FLC)(__FLC);
  assert 2===counter;
  counter = 0;
  o.s.p(__FLC)(__FLC)(__FLC);
  assert 3===counter;
  counter = 0;
  o['s'].('p')(__FLC);
  o['s'].s.('p')(__FLC);
  o['s'].s['p'](__FLC);
  assert 3===counter;
  o;
}

scope {
  var counter = 0, lastKey, lastVal;

  const f = proc(k,v){
    assert this.mayIterate()/*as of 20191212*/;
    counter = counter + 1;
    //print(k,v);
    lastKey = k;
    lastVal = v;
  };
  assert o.mayIterate();
  o.eachProperty(f);
  assert o.mayIterate();
  assert counter === o.propertyKeys().length();
  assert undefined !== lastKey;
  assert undefined !== lastVal;
  assert lastKey !== lastVal;
}

scope {
    /* Ensure that an object which inherits Function is call()able
       and that 'this' resolution works right. */
    var x = 0;
    var f = proc callee(){
        assert this inherits callee;
        assert this !== callee /* 'this' is one step down the prototype chain */;
        /* 'this' for non-property call() === the function resp. the call()able */;
        ++x;
        ++this.z;
    };
    var y = { prototype: f, z: 1 };
    assert y inherits f;
    y();
    assert 1 === x;
    assert 2 === y.z;
}

scope {
    var o = {
    };
    assert o.isEmpty();
    assert 0 === o.propertyCount();
    o.a = 1, o.b = 2;
    assert !o.isEmpty();
    assert 2 === o.propertyCount();

    var y = {};
    var x = o.copyPropertiesTo(y, {});
    assert x !== o;
    assert x !== y;
    assert 2 === x.propertyCount();
    foreach(x=>k,v){
        assert v === o[k];
        assert v === y[k];
    };

    assert 'CWAL_RC_TYPE' === catch{x.copyPropertiesTo(1)}.codeString();
    assert 'CWAL_RC_MISUSE' === catch{x.copyPropertiesTo(/*no args*/)}.codeString();
    assert 'CWAL_RC_TYPE' === catch{x.copyPropertiesTo.call(0/*not a container*/, 1)}.codeString();
}

scope {
    const o = {};
    assert o === o.withThis(proc(){});
    assert o === o.withThis(proc(){return});
    assert 1 === o.withThis(proc(){return 1});
}

o; // propagate this out to make sure it survives the propagation process w/o being cleaned up.
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<












































































































































































































































































































































Deleted bindings/s2/unit/100-050-overloading.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
/* Demonstrate operator overloading... */  

if(1){
  assert "3.00.1" === "3.0"+0.1 /* bug chasing */;

  var pCount = 0, mCount = 0, o = {
    a: 0,
    // Binary X+Y
    'operator+': proc(r){
      assert 1 === argv.length();
      ++pCount;
      return this.a + r;
    },
    // X+=Y
    'operator+=': proc(r){
      //print('operator+=',argv);
      assert 1 === argv.length();
      ++pCount;
      this.a += r;
      return this;
    },
    // X-Y
    'operator-': proc(r){
      //print('operator-',argv);
      assert 1 === argv.length();
      ++mCount;
      return this.a - r;
    },
    // X-=Y
    'operator-=': proc(r){
      //print('operator+=',argv);
      assert 1 === argv.length();
      ++mCount;
      this.a -= r;
      return this;
    },
    // X++
    'operator++': proc(){
      //assert this===argv.0 /* historical! */;
      assert !argv.#;
      ++pCount;
      this.a++;
      return this;
    },
    // ++X
    '++operator': proc(){
      assert !argv.0;
      ++pCount;
      ++this.a;
      return this;
    },
    // X--
    'operator--': proc(){
      //assert this===argv.0 /* historical! */;
      assert !argv.#;
      ++mCount;
      this.a--;
      return this;
    },
    // --X
    '--operator': proc(){
      assert !argv.0;
      ++mCount;
      --this.a;
      return this;
    },
    // -X
    '-operator': proc(){
      //print('-operator', argv);
      return -this.a;
    },
    // X==Y
    'operator==': proc(r){
      // very minimal impl which does not detect
      // an RHS of the same class as this.
      return ((typeinfo(name this)) === (typeinfo(name r)))
             ? this.a === r.a
             : this.a == r;
    },
    // X!=Y
    'operator!=': proc(r){
      return !(this==r);
    },
    // X<Y
    'operator<': proc(r){
      return ((typeinfo(name this)) === (typeinfo(name r)))
             ? this.a < r.a
             : this.a < r;
    },
    // X>Y
    'operator>': proc(r){
      return ((typeinfo(name this)) === (typeinfo(name r)))
             ? this.a > r.a
             : this.a > r;
    }
  };
  //print("Properties:", o.propertyKeys());
  assert(!o.prototype.'operator+');
  assert ++o === o;
  assert 1 === o.a;
  assert 1 === pCount;
  assert o++ === o;
  assert 2 === o.a;
  assert 2 === pCount;
  assert --o === o;
  assert 1 === o.a;
  assert 1 === mCount;
  assert o-- === o;
  assert 0 === o.a;
  assert 2 === mCount;
  //assert 2 === (o += 2);

  //print('o =',o);
  assert o === (o += 2);
  //print('o =',o);
  assert 4 === o + 2;
  assert 2 === o.a;
  assert 1 === o - 1;
  //assert 'CWAL_RC_NOT_FOUND' === catch{-o/*no such operator*/}.codeString();
  assert 'CWAL_RC_NOT_FOUND' === catch{+o/*no such operator*/}.codeString();
  //print("Properties:", o.propertyKeys());  
  assert 2 === o.a;
  o -= 1;
  assert 1 === o.a;
  assert -1 === -o;
  assert 1 === o.a;


  assert !(o < 1);
  assert o == 1;
  assert o != 2;
  assert o < 2;
  assert o > 0;

  assert 2 === ++o.a;
  assert 2 === o.a++;
  assert 3 === o.a;
  assert o == 3;
  assert !(3 == 0) /* b/c only LHS is checked for overloads */;

  assert catch{o * 1 /* no such op */};
  assert catch{o / 1 /* no such op */};
  assert catch{o % 1 /* no such op */};
  assert catch{o << 1 /* no such op */};
  assert catch{o >> 1 /* no such op */};

}

scope {
  const sproto = "".prototype;
  assert catch{"" * 3 /* no such op (yet) */};
  sproto.'operator*' = proc f(rhs){
    (rhs>0) || throw "Expecting positive RHS for STRING*N op.";
    f.buf || (f.buf = buffer(100));
    f.buf.reset();
    for(var i = 0; i < rhs; ++i ){
      f.buf.append(this);
    }
    return f.buf.toString();
  } using {buffer: s2.Buffer.new};
  assert '***' === "*" * 3;
  assert catch{'*' * -1 /* negative index */};
  assert "3.00.1" === "3.0"+0.1 /* checking for a misplaced bug */;
  unset sproto.'operator*';
}

var three = 3;
scope {
  var ar = [];
  ar.'operator+=' = proc(r){
    this.push(r);
    return this;
  };
  ar.'operator<<' = proc(r){
      return this.push(r);
  };
  ar += 1;
  ar << 2;
  assert '12' === ar.join('');

  var out;
  ar.'operator>>' = proc(r){
    //var code = r+" = this.pop()";
    //print('code =',code);
    //return eval -> code;
    return eval -> r+"=this.pop()";
  };
  1 ? (ar >> nameof out) // yet another (obscure) use for nameof
    : ar.pop();
  assert 2 === out;
  assert 1 === ar.length();
  assert 1 === ar.0;
  //print(out,ar);
  //print(__FLC, refcount three);
  ar[3] = three;
  //print(__FLC, refcount three);
  ;1;1;1;
  //print(__FLC, refcount three);
  ar.clear();
  //print(__FLC, refcount three);

  assert catch{ar & 1/* no such operator (yet) */};
  ar.'operator&' = proc(r){
    return this;
  };
  assert ar === ar & 1;


  var o = {a: [1,2]};
  o.a.'operator+=' = ar.'operator+=';
  o.a += 3;
  assert '123' === o.a.join('');

}
//print(__FLC, refcount three);
assert 3 === three;

scope {
  // Overloading math ops on functions...
  var x, setX = proc(){return x = argv.0};
  setX.'operator+' = proc(arg){return this(arg)};
  setX+1;
  assert 1 === x;
  assert 0 === setX + 0;
  assert 1 === setX + -1 + 1 * 2;
}

scope {
  // Overload-only -> op...
  var x, setX = proc(){return x = argv.0};
  setX.'operator->' = proc(arg){return this(arg)};
  setX->1;
  assert 1 === x;
  assert 0 === setX -> 0;
  assert -1 === setX ->( -1 * 1 ) /* -> has . precedence */;

  unset setX;

  // Overload-only =~ ("contains") and !~ ("does not contain") ops...
  var o = {
    a: [1,2,3],
    'operator=~':proc(arg){
      return this.a.indexOf(arg)>=0;
    }
  };
  assert o =~ 1;
  assert o =~ 3;
  assert !(o =~ -1);

  assert catch{o !~ 1 /* no such operator yet */};
  o.'operator!~' =proc(arg){
    return this.a.indexOf(arg)<0;
  };

  assert o !~ -1;
  assert !(o !~ 1);
  assert o =~ 1 && o !~ -1; // has comparison precedence
}

scope {
  // C++-style streams...
  var b = new s2.Buffer(20);
  // Remember that buffers are not containers:
  //b.prototype.'operator<<' = proc(self,arg){ // built-in overload
  //  return this.append(arg);
  //};
  b << "a" << "bc" << "def";
  assert "abcdef" === b.toString();
  b.reset();
  b << 1 << 2+3; 
  assert "15" === b.toString();
  //unset b.prototype.'operator<<';

  // Alternate implementation:
  var o = {
    buf: b.reset(),
    'operator<<': proc(arg){
       this.buf.append(arg);
       return this;
    }
  };
  o << "a" << "bc" << "def";
  assert "abcdef" === b.toString();
  b.reset();
  o << 1 << 2+3; 
  assert "15" === b.toString();
  //unset b.prototype.'operator<<';
}

scope {
    var x = 0;
    var obj = {
        'operator<<': proc(arg){
            x += arg;
            return this;
        }
    };
    proc(a,b,c){
        obj << a << b << c;
        assert 6 === x /* testing an argv propagation fix */;
        proc(d,e,f){
            obj << d << e << f /* testing an argv propagation fix */;
            assert 21 === x;
        }(4,5,6);
    }(1,2,3);
}

scope {
    var h = new s2.Hash(13);
    h.insert(1, "hi");
    assert 'hi' === h # 1;
    assert undefined === h # 0;
    var x = 0;
    assert 'hi' === h # (++x);

    assert 0 === catch{h#1 = 'error'}.message.indexOf('Invalid LHS')
    /* # is not valid in an assignment op */;

    var h2 = new s2.Hash(13);
    h2.insert(1,3);
    h2.insert('a', 0);
    h.insert(2, h2);
    assert 3 === h # 2 # 1;
    assert h # 2 # 1 === 3;
    assert h # 2 # 0 === undefined;
    assert h # 2 # 'a' === 0;

    h2.insert('f', proc f(){assert f===this; return 1});
    // # does not set 'this' like the dot op does
    assert 1 === (h # 2 # 'f')();
    // If it did, (h2#f)() would have h2 has 'this', which is
    // borderline disturbing. It also means this call syntax
    // won't work:
    // assert 1 === h # 2 # 'f'();

    var obj = { h: h, x:0 };
    assert catch{obj#1}.message.indexOf('Hash')>0
        /* LHS of # must be-a Hash. Overloading of # was removed,
           as it (A) will complicate extending # to support assignment
           (insert()) and (B) it essentially negates the reason
           for having the # operator: an access method for hashes which
           makes them competative with Objects (using Hash.search()
           has function call overhead which obj[prop] does not, making
           objects faster for most cases).
        */;
    assert 'hi' === obj.h # 1;
    assert undefined === obj.h # 0;
    assert obj.h # 2 # 'a' === 0;
    assert 3 === obj.h # 2 # (0+1);
    assert 0 === catch{obj.h#1 = 'error'}.message.indexOf('Invalid LHS')
    /* # is not valid in an assignment op */;;
}

scope {
    var o = {
        x: 3,
        'operator::': proc(key){
            return this.hasOwnProperty(key);
        }
    };
    assert o::x;
    assert !o::y;
}

0 && scope {
// experimental, doesn't yet work how i would like
// Certain parts of this require enabling/disabling
// specific ifdefs in s2_eval.c and/or s2_ops.c

  var o = {
    __typename: 'Bob',
    'operator->': proc(arg){
      return this.sub[arg];
    },
    sub:{
      __typename: 'Sub',
      x: -1,
      f: proc(){
        print(__FLC,'this =',this);
        assert 'Bob' === typename this;
        return 1;
      }
    }
  };
  print(o->'f');
  assert 'function' === typename o->'f';
  assert 1 === o->f(); // works, yet...
  assert 1 === o->'f'(); // works
  o->x = 1; // assigns in o (or errors, depending on how we assign engine->dotOpLhs)
  o->x++; // as well.
  // i understand why, but a fix seems rather intrusive. i'd rather have it not behave
  // like the dot, i think.
  print(__FLC,'o =',o);
};
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




















































































































































































































































































































































































































































































































































































































































































































































































































Deleted bindings/s2/unit/200-000-ifelse.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137


/* note that the semicolons here are needed for the assert, not the if/else blocks */
assert if(1){};
assert !if(0){throw __FLC};
assert !if(0){throw __FLC}else{};
assert if(0){throw __FLC} else if(1){} else{throw __FLC};

assert if(0){throw __FLC}
       else if(true)<<<_FI /*trued the second time*/;; _FI
       else {throw __FCL};
assert true /* Just checking tail consumption */;
assert !if(0){throw __FLC}
       else if(1-2+1){throw __FLC}
       else {};

var a;
if(0){throw __FLC}
else if(0){throw __FLC}
else{a=4*2-1} /* note no semicolon needed here */
assert 7 === a;
assert 7 === a /* make sure previous one wasn't skipped */;

a = 0;
//pragma(trace-token-stack 1);
if(0) throw __FLC; else if(1) a = 1; else throw __FLC;
//pragma(trace-token-stack 0);
assert 1 === a;

a = -1;
if(1) a = 0; else if(1) {throw __FLC} else {throw __FLC}
assert 0 === a;

a = 0;
if(0) throw __FLC; else if(0) {throw __FLC} else a = 1 ? 2 : 3;
assert 2 === a;

a = 0;
if(0) throw __FLC; else if(0) {throw __FLC} else {a = 0 ? 2 : 3}
assert 3 === a;

assert var ex = catch{
    if(0) 1; else if(0) else a = 1 ? 2 : 3;
    // --------------^^^^
};
assert 'CWAL_SCR_SYNTAX' === ex.codeString();
assert 0 < ex.message.indexOf('consecutive non-operators');

a = 0;
assert true === if(1) 1; else throw __FLC;
assert false === if(0) throw __FLC; else if(0) throw __FLC;
a = 1;
assert 1 === a /* make sure final if() DTRT */;

a = 0;
assert false === if(0) throw __FLC; else if(0) {0};
a = 1;
assert 1 === a /* make sure final if() DTRT */;

a = 0;
assert true === if(0) throw __FLC; else if(1) {0};
a = 1;
assert 1 === a /* make sure final if() DTRT */;

a = 0;
assert true === if(0) 1; else if(1) 0;
a = 1;
assert 1 === a /* make sure final if() DTRT */;

a = 0;
assert true === if(1) 1; else if(1) {throw __FLC};
a = 1;
assert 1 === a /* make sure final if() DTRT */;

a = 0;
assert true === if(1) 1; else if(1) throw __FLC;
a = 1;
assert 1 === a /* make sure final if() DTRT */;

// checking newline-as-EOX handling...
if(1){}
var b;
assert typeinfo(islocal b);

if(0){} else{}
var b2;
assert typeinfo(islocal b2);

if(0){} else if(0){}
var b3;
assert typeinfo(islocal b3);

if(0){}
var c;
assert typeinfo(islocal c);
/*
  When using heredocs as if/else bodies, a semicolon/newline
  between the heredoc's closing token and the "else" part is
  optional. Typing it seems to feel more intuitive, but internally
  heredocs are normally treated like script blocks, and a semicolon
  after a script block has a different meaning here:

  if(1){...}; else{...} // syntax error

  NEVERMIND: i don't like this semicolon inconsistency. But now it's
  doucmented :/
*/
var x;
if(1)<<<X x=1 X
else<<<Y Y // semicolon and EOL are equivalent at the END {} of LHS if/else
assert 1===x;

x = 0;
if(0)<<<X X
else<<<Y x=1 Y; // semicolon and EOL are equivalent at the END of the construct
assert 1===x;

if(0){// see comments above
    x = 0;
    if(1)<<<X x=1 X; // semicolon is a syntax error here for consistency with {blocks}.
    else<<<Y Y
    assert 1===x;
}

assert !catch{0 ? if(1) 1; else{2} : 1} /*no syntax error*/;
assert !catch{0 ? if(0) 1; else{2} : 1} /*no syntax error*/;
assert !catch{1 ? if(1) 1; else{2} : 1} /*no syntax error*/;
assert !catch{1 ? if(0) 1; else{2} : 1} /*no syntax error*/;
assert catch{1 ? if(0) 1; else 2 : 1}
/*
  Not exactly a bug: "unexpected ':'". The problem is one of scope:
  the if/else parts run in a new scope and it's there that the else
  encounters ':', but the ternary op state does not span scopes (we
  explicitely save/reset/restore it when pushing/popping
  scopes). Terminating the 'else' body with a semicolon OR using a {}
  body instead of non-{} body resolves it.
*/;
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


















































































































































































































































































Deleted bindings/s2/unit/200-100-while.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

var i = 0, max = 1000;
while(i < max){i = i + 1}
assert max===i;

assert 'hi' === while( true ){ break "hi" };

assert undefined === while(false){throw __FLC};

assert 1 === while(true){ scope {catch {break 1} };; };

assert 'CWAL_SCR_SYNTAX' === catch{
       while(/*empty expr is not OK*/){/*empty body is OK*/}
}.codeString();

i = 0;
while(!i){
  var x =0;
  i = while(true){
    if((x=x+1)>3){break x}
    //(x=x+1)>3 && break x
  }; /* semicolon needed b/c of assignment expr */
  true // at EOF, semicolon is optional
}
assert 4===i;

i = 0;
while(!i){
  var x =0;
  i = while(true){
    if((x=x+1)>3){break x}
    //(x=x+1)>3 && break x
  } /* semicolon _normally_ needed b/c of assignment expr,
       but special-casing of keywords ending in blocks
       implicitly treat this EOL as an EOX */
}
assert 4 === i;

i = 0;
assert 4 === while(true){
  ((i = i + 1)<4) && continue;
  break i;
};

i = while(false){};
assert undefined === i;

i = while(true){break};
assert undefined === i;

i = 0;
while(i<3)++i;
assert 3 === i;
assert 3 === i /* make sure the previous line wasn't snarfed by the loop */;

i = 0;
while(i++<3);
assert 4 === i;
assert 4 === i /* make sure the previous line wasn't snarfed by the loop */;

i = 0;
assert !catch{0 ? while(i<2) ++i : 1} /* no syntax error */;
assert !i;
assert !catch{1 ? while(i<2) ++i : 1} /* no syntax error */;
assert 2 === i;
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


































































































































Deleted bindings/s2/unit/200-200-for.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

for(var i = 1, x = -1;
        i >= -1;
        i -= 1, x+=1){
  //print('for(): i =',i,'x =',x);
}
assert 'undefined' === typename i;

var i = 0, x = for(;;){
  (i = i + 1)>3 && break i
};
assert 4 === i;
assert x === i;

x = for(i = 0;;){
  scope {
    catch{ (i = i + 1)>4 && break i }
  }
};
assert 5 === i;
assert x === i;

assert -1 === (1-2 ||| for(;;){/* if this infinite loops then skip mode is broken*/});
assert -1 === eval 1-2 ||| for(;;){/* if this infinite loops then skip mode is broken*/};
assert false === (0 && for(;;){/* if this infinite loops then skip mode is broken*/});
assert false === eval 0 && for(;;){/* if this infinite loops then skip mode is broken*/};


for(i = 0, x = 0;
    i < 3;
   ) ++i, x += 2;
assert 3 === i;
assert 3 === i /* make sure the previous line wasn't snarfed by the loop */;
assert 6 === x;

for(i = 0, x = 0; i < 3; ++i
) x += 2;
assert 3 === i;
assert 3 === i /* make sure the previous line wasn't snarfed by the loop */;
assert 6 === x;

for(i = 0, x = 0; i < 3; ++i    );
assert 3 === i;
assert 3 === i /* make sure the previous line wasn't snarfed by the loop */;
assert 0 === x;

assert !catch{0 ? for(x = 0; x<1; ++x) 1 : 1} /* no syntax error */;
assert !catch{1 ? for(x = 0; x<1; ++x) 1 : 1} /* no syntax error */;
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
































































































Deleted bindings/s2/unit/200-300-dowhile.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

var i;
i = 0;
do{++i}while(i<5);
assert 5 === i;
i = 0;
do ++i; while(i<5);
assert 5 === i;

assert 'CWAL_SCR_SYNTAX' === catch{
    do ++i while(true) /* missing semicolon after ++i */
}.codeString();

i = 0;
do{
    ++i
}
while(!i);
assert 1 === i;

assert -1 === do{
    break -1;
}while(true);

assert undefined === do ; while(false)
  /* if a for/while loop body may be empty, why not a do loop, too? */
;


i = 0;
do ++i>2 && break; while(true);
assert 3 === i;

i = 0;
do i++>2 && break; while(true);
assert 4 === i /* yes, four */;

i = 0;
assert !catch{0 ? do ++i; while(i<2) : 1} /* no syntax error */;
assert !i;
assert !catch{1 ? do ++i; while(i<2) : 1} /* no syntax error */;
assert 2 === i;
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




















































































Deleted bindings/s2/unit/300-000-functions.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
var x = function X(a=1,b=0){
  //print("argv =",argv);
  assert 'array' === typename argv;
  assert 'function' === typename X;
  //print('"this" type =',typename this);
  return a + b;
};
assert 'function' === typename x;
assert 1 === x(1);
assert 3 === x(1,2);
//return;
assert 3 === x(1,2,3);

var o = { f: x };
assert -1 === o.f(0,-1,1);
unset o;

var y = 1 || proc(){skipped}(args,skipped,too);

// just to watch the dtor in debug output...
// print('unsetting func'); unset x; print('after unset');

unset x, y;
// Fixed :-D
//print('script location info from inside funcs will be wrong for a while:',
//      catch proc(){throw    __FLC}()
//);
assert __LINE+1 === catch {
  proc(){throw 0}()
}.line;

var recursive = proc r(a=1){
  return a>3 ? a : r(a+1);
};

assert 4 === recursive();

assert 4 === proc rec(a=1){
  return a>3 ? a : rec(a+1);
}();
assert 'undefined' === typename rec;

var x = function myfunc(a=myfunc.defaults.0, b=myfunc.defaults.1){
  return a + b;
};
x.defaults = [1,-1];
assert 0 === x();
assert 1 === x(2);

var o = {
  name: 'fred',
  f:function(){return this.name}
};
assert 'fred' === o.f();

scope {
    var x = 3, y = 4;
    var f = function(c){
        return a + b + c;
    }.importSymbols({a: x, b: y});
    assert 8 === f(1);
    f.a = 1;
    assert 8 === f(1) /* that's a different 'a' */;
    var counter = 0;
    f.eachProperty(proc(){
        counter = counter + 1;
        //print(argv);
    });
    assert 1 === counter
    /* making sure that the importSymbols sym is hidden (does not get
       iterated over). */;
    f.clearProperties();
    assert 9 === f(2) /* f.clearProperties() does not nuke imported
                         symbols, as of 20160206. */;
    f.importSymbols(false,{b: 3})
    /* initial bool arg specifies whether or not to clear
       existing imported properties before the import.
       Added 20171227.
    */;
    assert 10 === f(4);
}

scope {
  var o = {a:1};
  var f = function(a){
    return this.a + a;
  };
  assert 2 === f.apply(o,[1]);
  assert 2 === f.call(o,1);

}

scope {
  // Make sure that skip mode is honored
  // for default parameters which are passed
  // in by the caller...
  var f = proc(a, c=(throw 'default param threw'), b=1){
    assert 1===b;
    assert 0===c;
    assert -1===a;
  };
  f(-1,0);
  assert 0 === catch{f(1) /* param c=throw... gets processed */}
         .message.indexOf('default param');

  // make sure sourceCode() is working.
    assert f.sourceCode().indexOf("default param threw")>8;
    assert proc(){/*comment*/}.sourceCode() === "proc(){}";
    assert proc(/*comment*/){/*comment*/}.sourceCode() === "proc(){}";
}

scope {
    /* The "using" pseudo-keyword... */
    var x = 30, f = proc() using(x) {return x};
    unset x;
    assert 30 === f();
    var f2 = proc() using({y:20, F:f}) {return F() * y};
    assert 600 === f2();
    f.importSymbols({x: 40});
    unset f;
    assert 800 === f2();
    unset f2;

    /* Alternate placement at the end of the function, primarily
       because it makes migration from importSymbols() a simple
       search/replace op: */
    var x = 30, y = 20, f = proc() {return x*y} using(x, y);
    unset x, y;
    assert 600 === f();
    var f2 = proc() {return F() * y} using({y:10, F:f});
    assert 6000 === f2();
    f.importSymbols({x: 40, y: 1});
    unset f;
    assert 400 === f2();
    unset f2;

    /* Another alternate syntax which takes a single object literal.
       It does not accept an object expression or a hash literal.
    */
    assert 60 === proc() using {A: 30} { return A * argv.0 }(2);
    assert -60 === proc() { return B * argv.0 } using {B: -30}(2);

    assert 'CWAL_RC_TYPE' === catch {
        proc() using {#a:1} {}
    }.codeString() /* hash literal is not allowed */;

    assert catch {
        proc() using (true) {}
    }.message.indexOf("eyword") > 0 /* keyword as identifier not allowed */;

    assert catch {
        proc() using () {}
    }.message.indexOf("mpty") > 0 /* empty expression not allowed */;

    /* Contrast empty () with empty {object}, which is allowed, though
       not of much use... */
    assert 0 === proc() using {} {return 0}();

    assert catch {
        var x;
        proc() using (x ?) {}
    }.message.indexOf("nexpected") > 0 /* illegal token */;

    assert catch {
        var x;
        proc() using (x, 1) {}
    }.message.indexOf("'integer'") > 0 /* illegal expr. type */;

    assert catch {
        proc() using x{}
    }.message.indexOf("Expecting (...)") === 0 /*using requires () or {}*/;

    assert catch {
        proc() {} using x
    }.message.indexOf("Expecting (...)") === 0 /*using requires () or {}*/;


    // Demonstrate how using() deals with identifiers vs non-identifier/non-literal
    // expressions:
    var a = 0;;
    assert catch {
        proc() using(a.prototype, b) {}
    }.message.indexOf("Unexpected token '.'") === 0
    /* fails b/c a leading identifier triggers import of that identifier
       and does not eval a sub-expression. */;
    assert proc(){} using((a.prototype)) /* works b/c it doesn't start with an identifier */;
    assert proc(){} using(0.prototype) /* works b/c it doesn't start with an identifier */;
    unset a;
    var obj = {a:1, b:2};
    var f1 = proc(){return obj.a+obj.b} using(obj);
    var f2 = proc(){return a+b} using((obj));
    unset obj;
    assert 3 === f1() /* because using(obj) resolves obj as an identifier. */;
    assert 3 === f2() /* because using((obj)) triggers a sub-expression parse and
                         imports the resulting properties. */;
}

scope {
    // testing a parsing fix for prefix ++/-- ...
    var f = proc(){return o} using{o:{x:1}};
    assert 1 === f().x;
    assert 1 === f().x++;
    assert 3 === ++f().x;
    assert 2 === --(f()).x;
    //assert 3 === ++(f().x) /* doesn't work b/c (x) is resolved in a subexpression */;
}

var u = scope {
    /* 20191209: using() keyword */
    assert undefined === proc(){return using()}();
    var f = proc(){return using()} using{a: 1};
    var u = f();
    assert typeinfo(isobject u);
    assert !u.prototype;
    assert 1 === u.a;
    u.b = 2;
    assert 2 === f().b;
    assert undefined ===  using(proc(){});
    assert 'CWAL_SCR_SYNTAX' === catch {using()}.codeString();
    assert 'CWAL_RC_TYPE' === catch using(f.apply).codeString();
    f = proc(){return using()} using{}
    /* as fate would have it, using{} is legal, and creates an empty
       imports object, but using() is not. The latter was an
       intentional design decision but the former just a happy
       accident. */;
    u = f();
    assert typeinfo(isobject u);
    u.b = 1;
    assert 1 === f().b;

    /* imports must survive propagation out of a temporary value... */
    u = using(proc()using{a:999}{});
    ;;;;;;;; /* pedanticness: make sure sweepup triggers (though the temp function is already gone by now) */
    assert typeinfo(isobject u);
    assert 999 === u.a;

    /**
       20191211: using (without parens) is legal in function bodies as a shorthand
       for using() (it's also more efficient).
    */
    assert catch {
        using;
    }.message.indexOf("Expecting (") === 0;
    f = proc F()using{a:1}{
        assert typeinfo(islocal a);
        assert using(F) === using();
        proc(){assert undefined===using /* not inherited from outer function */}();
        return scope {using.a /*from containing function*/}
    };
    assert 1 === f();

    /**
       20191211: adding a dot after the "using" clause in a function
       definition specifies that the imported symbols (whether via
       this "using" decl or a future importSymbols() call) are NOT to
       be declared as local variables at call()-time. Instead, they
       may be accessed via the call-local "using" keyword.
    */
    f = proc F() using.{XXX: 1} {
        assert !typeinfo(islocal XXX);
        assert typeinfo(islocal F);
        assert 1 === using.XXX;
        assert 1 === using()['XXX'];
        assert using.XXX === using(F).XXX;
        return using['XXX'];
    };
    assert 1 === f();

    assert 1 === proc(){
        assert !typeinfo(islocal a);
        return using.a;
    } using.{a:1}();
    /* 20191218 bugfix test:
       1) Order of {body}using.{...} broke parser.
       2) S2_FUNCSTATE_F_NO_USING_DECL flag was not being set
       on the function when that order applied. 
    */
    ;

    /* Confirm lifetime of imported symbols for functions defined
       inside using(<here>)... */
    assert "!wakawaka!" === using(proc(){}using{a:"!wakawaka!"}).a;
    assert "/foo/bar/baz" === scope using(proc()using{a:"/foo/bar/baz"}{}).a;
    var uo = using(proc()using{a:"/foo/bar/baz", b: 1000}{});
    assert "/foo/bar/baz" === uo.a;
    assert 1000 === uo.b;
    unset uo;
    assert using(proc(){}using{a:"!wakawaka!"}).a
        === proc(){return using()}using{b:"!wakawaka!"}().b;
    
    /* imports can be added after the fact with importSymbols()... */
    f = proc(){return using()};
    assert undefined === f();
    f.importSymbols({a: 1000});
    assert 1000 === f().a;
    /* imports must survive propagation out of this scope... */
    f() /* scope result */;
};
assert typeinfo(isobject u);
assert 1000 === u.a;

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


























































































































































































































































































































































































































































































































































































































Deleted bindings/s2/unit/400-000-new.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
proc(){
    assert !typeinfo(isnewing);
}();
assert !typeinfo(cannew 1);
assert !typeinfo(cannew {});
assert typeinfo(cannew s2.Hash);
var v = new s2.Hash(13);
assert v inherits s2.Hash;
assert 13 === v.hashSize();
//s2.dumpVal(v);

assert typeinfo(cannew s2.Buffer);
v = new s2.Buffer(10);
assert v inherits s2.Buffer;
assert v.capacity() >= 10;
//s2.dumpVal(v);

assert typeinfo(cannew s2.PathFinder);
v = new s2['PathFinder']() {
    this.x = 1;
};
assert v inherits s2.PathFinder;
assert undefined === v.search('PathFinder');
assert 'string' === typeinfo(name v.search(__FILE));
assert 1 === v.x;
//s2.dumpVal(v);

const Array = {
    prototype: [].prototype,
    blah: 2,
    __new: proc(){
        this.x = 1;
        assert typeinfo(isnewing);
        assert typeinfo(is-newing this);
        assert argv && !typeinfo(isnewing argv);
        if(1) assert typeinfo(isnewing) /* works as of 20171206 */;
        assert typeinfo(isnewing) /* But now we're back in our 'new' scope. */;
        proc(){assert !typeinfo(is-newing)/* different "this" */}();
        var rc = argv.slice();
        rc.prototype = this;
        return rc;
    }
};
assert typeinfo(cannew Array);
v = new Array(-1,0,1);
assert v inherits Array;
assert 'array' === typeinfo(name v);
assert [].prototype === Array.prototype;
assert -1 === v.0;
assert 1 === v.2;
assert 2 === v.blah;
assert v.blah === (v[] = v.blah); // reminder to self: not a precedence bug - JS also requires extra parens here.
assert 3 === ++v.blah;
assert 2 === v.prototype.blah;
assert 2 === v.prototype.prototype.blah;
assert 3 === v.blah;
assert 4 === v.length();
v[4] = -1;
assert 5 === v.length();
assert -1 === v.4;
//s2.dumpVal(v);
//v.eachIndex(proc(v){print(typename v, v)});

const Object = {
    prototype: {}.prototype,
    __new: proc(){
        assert typeinfo(isnewing);
        return;// {prototype: this}; // same effect but requires +1 temp Object allocation
    }
};
assert typeinfo(cannew Object);
v = new Object();
assert v inherits Object;
assert 'function' === typename v.eachProperty;
assert v.isEmpty();

const String = {
    prototype: "".prototype,
    __new: proc(){
        // hmmm. Can't do POD types as PODs this way
        // without other s2-level infrastructure.
        this.value = argv.join('');
        assert typeinfo(isnewing);
    }
};
assert typeinfo(cannew String);
v = new String("a","b","c");
assert v inherits String;
assert v !== String;
assert 'abc' === v.value;
assert undefined === v.prototype.value;

// An alternate approach...
const String2 = {
    __new: proc(){
        assert typeinfo(isnewing);
        return argv.join('');
    }
};
assert typeinfo(cannew String2);
v = new String2('a','b','c');
assert v === 'abc';

const Boolean = {
    __new: proc(v=(affirm 0 && "Boolean ctor requires an argument")){
        assert typeinfo(isnewing);
        return !!v;
    }
};
assert typeinfo(cannew Boolean);
assert true === new Boolean(true);
assert false === new Boolean(false);
assert catch {new Boolean()}.message.indexOf('ctor') > 0;
assert catch new Boolean().message.indexOf('ctor') > 0; // also works

const MyClass = {
    prototype: s2.Hash,
    __new: proc(){
        assert typeinfo(isnewing) /* in 'new' scope */;
        if(1){
            assert typeinfo(isnewing);
            const x = this;
            proc(){
                assert !typeinfo(isnewing)/*not the same 'this' as the ctor call*/;
                assert typeinfo(isnewing x)/*optionally accepts an expression*/;
            }();
        }
        var rc = new s2.Hash();
        rc.prototype = this;
        foreach(@argv=>i,v) rc.insert('k'+i, v);
        proc(){
            assert !typeinfo(isnewing) /* not in 'new' scope */;
            new String() /* typeinfo(isnewing) check must pass */;
            assert !typeinfo(isnewing) /* not in 'new' scope */;
        }();
        new Boolean(1) {
            assert !typeinfo(isnewing) /* not in the post-ctor block */;
        };
        assert typeinfo(isnewing) /* back in 'new' scope */;
        return rc;
    }
};
assert typeinfo(cannew MyClass);
v = new MyClass(1,2,3);
assert v inherits MyClass;
assert MyClass inherits s2.Hash;
assert v inherits s2.Hash;
// v's prototype is a hash, but v is not, so new properties
// set via its dot/[] ops don't land in the hash:
//v.eachEntry(proc(k,v){print(__FLC,' ',k,'=',v)});
assert 2 === v#'k1';
assert 'function' === typeinfo(name v.__new);
assert 3 === v.search('k2');
assert 3 === v # 'k2';


/* Exception subclasses are a bit trickier... */
const E = {
    prototype: exception(0).clearProperties(),
    __typename: 'Exceptional',
    __new: proc(codeOrMsg,
                msgOrCode){
        assert typeinfo(isnewing);
        (argv.length()>1
         ? exception(codeOrMsg,msgOrCode)
         : exception(0,codeOrMsg)
        ).copyPropertiesTo(this);
        //e.copyPropertiesTo(this)
        /* collect stack trace and friends */;
        this.hi = 1;
    }
};
assert !E.stackStrace;
assert !E.script;
var e = catch throw new E('yo');
assert e;
assert e.prototype === E;
assert e inherits E;
assert !E.stackStrace /* testing a bufix in s2_throw_value() */;
assert typeinfo(hasexception e);
assert !typeinfo(isexception e);


/* Make sure short-circuiting skips all it's supposed to... */
assert !typeinfo(isdeclared Nope);
assert -1 === 0 ? new Nope() : -1;
assert -1 === 0 ? new Nope() { nope nope nope } : -1;
assert -1 === (1 ? new MyClass() { this.x = -1 } : new Nope() { nope nope nope }).x;

// reminder to self: braces are needed around these catch blocks because
// otherwise catch does not convert syntax errors to exceptions.
assert 'CWAL_SCR_SYNTAX' === catch { new MyClass() {return 3} }.codeString();
assert catch { new MyClass() { continue } }.message.indexOf('Unhandled') >= 0;
assert catch { new MyClass() { break } }.message.indexOf('Unhandled') >= 0;

assert 3 === new MyClass(), 3 /* new must stop after the call() part... */;
assert 3 === new MyClass(){}, 3 /* ... or the post-init part */;

if(0){
    // Functions as ctors is currently disabled
    /**
       This is more JavaScripty but also leads to each instance being
       able to be call()'d, and that call landing in the constructor!
       Unlike in JS, this ctor _is_ the prototype for new instances,
       so add shared methods directly as properties of this ctor.
       
       i don't have a workaround for the call()able problem which also
       leaves the resulting objects as (v inherits Func). :/
    */
    const Func = proc(a,b){
        this.a = a;
        this.b = b;
    };
    assert typeinfo(cannew Func);
    var f = new Func(1,2);
    assert f inherits Func;
    assert f.a === 1;
    assert f.b === 2;
    assert 'object' === typeinfo(name f);
    assert !typeinfo(isfunction f);
    assert typeinfo(iscallable f) /* unfortunate */;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




























































































































































































































































































































































































































































Deleted bindings/s2/unit/500-000-array.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
var ar = [1,2,3];

scope {
  assert 3 === ar.#;
  assert 3 === ar.#;
  assert ar.# === 3;
  assert '3' === ar.#.toString();
  assert 2 === ar.indexOf(3);
  assert ar === ar.length(2) /* setter returns the array */;
  assert 2 === ar.#;
  assert 2 === ar[1];
  assert undefined === ar[2];

  ar.prop = 1;
  ar.clear();
  assert 1===ar.prop;
  assert !ar.#;
  ar.clear(true);
  assert undefined===ar.prop;
}

scope {
  assert ar.isEmpty();
  assert 3 === ar.push(1,2,3);
  assert !ar.isEmpty();
  assert 3 === ar.#;
  var x = 0;
  var eachCallback = proc(v,i){
    //print('in func x=',x,i);
    x = i;
      //print(x, argv, v, i);
  } ;
  ar.eachIndex( eachCallback );
  assert 2 === x;
  eachCallback(0,1);
  assert 1 === x;
}

scope {
  assert 3 === ar.#;
  assert 3 === ar.2;
  assert 3 === ar.pop();
  assert 2 === ar.#;
  assert 1 === ar.shift();
  assert 1 === ar.#;
  assert 2 === ar.0;
  assert 0 === ar.indexOf(2);
  ar.reverse();
  assert 2 === ar.0;
}

scope {
    // Array.indexOf() type-strict comparison flag...
    var a = [0,'1',2];
    assert a.indexOf(1,false) === 1 /* not type-strict */;
    assert a.indexOf(1,true) < 0 /* type-strict */;
    assert a.indexOf('1',false) === 1 /* not type-strict */;
    assert a.indexOf('1',true) === 1 /* type-strict */;
    assert a.indexOf(1) < 0 /* default == type-strict */;
}

scope {
  assert 1 === ar.#;
  assert 2 === ar.0;
  ar.unshift(1,-1);
  assert 3 === ar.#;
  assert 2 === ar.2;
  assert -1 === ar.1;
  assert 1 === ar[0];
  assert '1,-1,2' === ar.join(',');
  assert 3 === ar.setIndex(1,3);
  assert '1,2,3' === ar.sort().join(',');
  assert '3,2,1' === ar.sort(function(l,r){
    // reverse-order (l,h)...
    return l==r ? 0 : l < r ? 1 : -1;
    cannot happen;
    // The long way:
    l==r && return 0;
    l < r && return 1;
    return -1;
  }).join(',');
  assert '123' === ar.reverse().join('');
  assert ar === ar.unshift(-1);
  assert '-1123' === ar.join('');
}

scope {
  const a1 = [1,2,3];
  assert catch{a1.slice(0,-1)}.codeString() === 'CWAL_RC_RANGE';
  assert catch{a1.slice(-1,0)}.codeString() === 'CWAL_RC_RANGE';
  // slice() during sort is madness...
  assert catch a1.sort(proc(){a1.slice()}).codeString() === 'CWAL_RC_LOCKED';
    
  var a2 = a1.slice(0,1);
  assert 'array' === typename a2;
  assert 1 === a2.#;
  assert 1 === a2.0;

  const a3 = a2.slice(3);
  assert 0 === a3.#;
}

scope {
  const a1 = [1,2,3];
  var a2 = a1.slice(0, 1);
  //print('a2=',a2);
  assert 1=== a2.#;

  a2 = a1.slice(1,1);
  assert 1=== a2.#;
  assert 2 === a2.0;

  a2 = a1.slice(1, 6);
  assert 2=== a2.#;
  assert 3 === a2.1;
}

scope {
    const a = [-2,-1,0,1];
    assert -2 === a.shift();
    assert -1 === a.0;
    assert 0 === a.shift(2);
    assert 1 === a.#;
    assert 1 === a.0;
}

scope {
    const ar = [0,proc f(){assert f===this; return this},1];
    assert ar.1 === ar.1() /* 'this' is not the array */;
}

scope { // using arrays as prototypes...
    var x = {0: 0, // interesting: whether or not this property
             // gets set as an object prop or array index depends
             // on whether it gets set before or after this object
             // extends the array class...
             prototype:[1,2]
            };
    x.0 = -1 /* sets array index */;
    assert -1 === x.0 /* array index */;
    assert -1 === x.prototype.0 /* array index */;
    if(!pragma(build-opt CWAL_OBASE_ISA_HASH)){
        assert 0 === x.'0' /* string type bypasses array index check,
                              so this is an object property access.*/;
        x.'0' = 1 /* also object property, not array index */;
        assert -1 === x.0 /* string key '0' did not overwrite this */;
    }
    x[] = 3;
    assert 3 === x.length();
    assert 3 === x.2;

    /*
      Minor descrepancy vis-a-vis objects: objects never (via
      assignment) add new properties to their prototypes, but instead
      shadow any prototype prop with the same name. Arrays index
      access does not behave that way: it modifies the array part of
      the value directly (the prototype, in the above example).
    */
}

scope {
    // Check for propagation of non-exception errors from array.sort()
    // callbacks...
    const ex = catch [1,2].sort(proc(){
        ,/*intentional syntax error*/
        assert cannot happen;
    });
    assert 'CWAL_SCR_SYNTAX' === ex.codeString();
}

scope {
    // Ensure that attempts to iterate over an array from a sort()
    // callback, or sort during iteration, are rejected...
    const ar = [1,2,3];
    var ex = catch foreach(@ar=>v) ar.sort();
    assert 'CWAL_RC_IS_VISITING_LIST' === ex.codeString();
    ex = catch ar.sort(proc(){foreach(@ar=>v){1}});
    assert 'CWAL_RC_LOCKED' === ex.codeString();

    // But multiple iteration must (as of 20191211) work...
    var i = 0;
    foreach(@ar=>v) foreach(@ar=>v) ++i;
    assert i === ar.# * ar.#;

    // Assigning during traversal is OK:
    foreach(@ar=>i,v) ar[i] = 2*v;
    assert 6 === ar.2;

    // reverse() during traversal is allowed only because it's
    // just a special case of assignment:
    foreach(@ar=>v) ar.reverse();
    assert 6 === ar.0;
    assert 2 === ar.2;
    /**
       One could rightfullyargue that sorting is also just a special
       case of get/set, but its implementation is much more involved,
       so traversing during sort, or vice versa, are currently
       disallowed.  That Way Lies Madness.
    */
}

scope {
    // Ensure that sort() never calls its callback for an empty- or
    // length-1 array...
    [].sort(proc(){cannot happen});
    [1].sort(proc(){cannot happen});
}

scope { // removeIndex()
    var x = [1,2,3,4];
    assert true === x.removeIndex(1);
    assert 3 === x.#;
    assert 1 === x[0];
    assert 3 === x[1];
    assert 4 === x[2];
    assert undefined === x[3];
}

scope { // filter()
    const isOdd = proc(v){ return v % 2 };
    var a = [1,2,3];
    var b = a.filter(isOdd);
    var c = a.filter(isOdd, true);
    assert 2 === b.#;
    assert 1===b.0 && 3===b.1;
    assert 1 === c.#;
    assert 2 === c.0;
    unset a, b, c;

    /**
       20181109: Array.filter() now accepts a Tuple 'this'. As of
       20190811, it returns a Tuple if called that way (it previously
       returned an array).
    */
    const t = [#1,2,3,4];
    assert typeinfo(istuple t);
    var ta = t.filter(isOdd);
    assert typeinfo(istuple ta);
    assert 2 === ta.#;
    assert 1 === ta.0;
    assert 3 === ta.1;
    assert [#1,3] === ta;

    ta = t.filter(isOdd,true);
    assert typeinfo(istuple ta);
    assert [#2,4] === ta;
    
    assert [#] === [#].filter(isOdd);

}

scope { // operator+=
    var a = [] /* can't be const because of += assignment :/ */;
    assert a === (a+=3);
    assert 1 === a.#;
    assert 3 === a.0;
    assert a === (a += 'b');
    assert 2 === a.#;
    assert 'b' === a.1;
    const o = {a:[]};
    assert o.a === (o.a+=1);
    assert (o.a += 2) === o.a;
    assert 2===o.a.1;
}

scope {
    /* 20191211 changes to how iteration is reported... */
    var a = [1,2,3];
    a.x = -1;
    assert !typeinfo(isiteratingprops a);
    assert !typeinfo(isiterating a);
    foreach(a=>k){
        assert typeinfo(mayiteratelist a);
        assert !typeinfo(isiteratinglist a);
        assert typeinfo(isiteratingprops a);
        assert typeinfo(isiterating a);
        foreach(@a=>v){
            assert typeinfo(isiteratinglist a);
            assert typeinfo(isiteratingprops a);
            assert typeinfo(mayiteratelist a);
            assert typeinfo(isiterating a);
        }
        assert !typeinfo(isiteratinglist a);
        assert typeinfo(isiterating a);
        assert typeinfo(isiteratingprops a);
    }
    assert !typeinfo(isiteratingprops a);
    assert !typeinfo(isiterating a);

    a.sort(proc(l,r){
        assert !typeinfo(mayiteratelist a);
        return -l.compare(r);
    });
    assert 3 === a.0;
}

scope {
    /*
      Array comparison func must internally use floating point
      comparisons.

      Changed 20191220, prompted by a bug repor against the muJS JS
      engine: https://github.com/ccxvii/mujs/issues/122
    */
    const l = [{a: 0.5}, {a: -0.1}, {a: 0.7}];
    l.sort(proc(x,y) {return x.a - y.a});
    assert 0.7===l.2.a;
    assert 0.5===l.1.a;
    assert -0.1===l.0.a;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<












































































































































































































































































































































































































































































































































































































































Deleted bindings/s2/unit/600-000-hash.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
const Hash = s2.Hash.new;

scope {
  var h = Hash();
  assert h inherits s2.Hash;
  var tableSize = h.hashSize();
  assert 'integer' === typename tableSize;
  assert tableSize > 0;
  unset tableSize;
  assert 0 === h.entryCount();

  h.insert(1,-1.0);
  assert 1 === h.entryCount();
  assert -1.0 === h.search(1);
  h.insert(0,1.0);
  var c = 0;
  h.eachEntry(function(k,v){
    assert 'integer' === typename k;
    assert 'double' === typename v;
    c = c + 1;
  });
  assert h.entryCount() === c;

  assert h.containsEntry(1);
  assert !h.containsEntry(2);

  var keys = h.entryKeys(), vals = h.entryValues();
  assert 'array' === typename keys;
  assert 'array' === typename vals;
  assert vals.length() == c;
  assert keys.length() == c;
  unset keys, vals;

  h.remove(-1);
  assert 2 === h.entryCount();
  h.insert(1, 0.0);
  assert 2 === h.entryCount();
  h.remove(1);
  assert 1 === h.entryCount();
  h.clearEntries();
  assert 0 === h.entryCount();
}

scope {
    var h1 = s2.Hash.new(11), h2 = s2.Hash.new(23);
    h1.insert(1, 2);
    h1.insert(2, 3);
    h1.eachEntry(h2, h2.insert);
    assert 2 === h2.entryCount();
    h1.clearEntries();
    assert !h1.entryCount();
    h2.eachEntry(h1);
    assert 2 === h1.entryCount();

    var o = {};
    h1.eachEntry(o, o.set);
    assert 3 === o.2;
    assert 2 === o.1;

    h2.clearEntries();
    h1.eachEntry(h2);
    assert 2 === h2.entryCount();
    assert 3 === h2.search(2);
    assert 2 === h2.search(1);

    h1.insert('f', proc f(){
        assert f === this;
        return this;
    });

    assert h1#'f' === (h1#'f')();
    assert h1#'f' === h1#'f'();
}

scope {
    var h = s2.Hash.new(11);
    assert !h.hasEntries();
    assert 11 === h.hashSize();
    h.insert(1, 2);
    assert h.hasEntries();
    h.insert(3, 4);
    assert 2 === h.entryCount();
    h.resize(3);
    assert 3 === h.hashSize();
    assert 4 === h # 3;
    assert 2 === h # 1;
    assert 2 === h.entryCount();
    assert h.hasEntries();
    assert 'CWAL_RC_RANGE' === catch{h.resize(-1)}.codeString();
    assert 'CWAL_RC_MISUSE' === catch{h.resize()}.codeString();
}

if(s2.getResultCodeHash){
    const rch = s2.getResultCodeHash();
    assert rch.entryCount() > 90 /* === 102 as of this writing */;
    assert rch === s2.getResultCodeHash() /* result is cached */;
    assert 'integer' === typename rch # 'CWAL_RC_OOM';
    assert 'string' === typename rch # 0 /* the only code with a well-defined value! */;
}

scope{
    var src, h;
    const reset = proc(){
        src = {a:1, b:-1, c:0};
        h = s2.Hash.new(5);
    };
    reset();
    h.insert('a', 'aaaa');
    h.takeProperties(src,1);
    assert 1 === h # 'a';
    assert undefined === src.a;
    assert undefined === src.b;
    assert undefined === src.c;
    assert(src.isEmpty());

    reset();
    h.insert('a', 'aaaa');
    h.takeProperties(src,-1);
    assert 'aaaa' === h # 'a';
    assert 1 === src.a;
    assert undefined === src.b;
    assert undefined === src.c;
    assert(!src.isEmpty());

    reset();
    h.insert('a', 'aaaa');
    const ex = catch h.takeProperties(src,0);
    assert ex;
    assert 'CWAL_RC_ALREADY_EXISTS' === ex.codeString();
    assert 1 === src.a;
    /* src.b and src.c might have been moved already:
       internal order is undefined and mutable
       at runtime. */
    assert !src.isEmpty();
}

scope { // 2021-07-24: X.takeProperties(X) (dest==src)
    const h = {#a:1, b:2};
    assert 2 === h.#;
    h.a = 3;
    assert 3 === h.a;
    assert 1 === h#'a';
    assert 1 === h.propertyCount();
    h.takeProperties(h);
    assert 0 === h.propertyCount();
    assert 3 === h#'a';
    assert 2 === h.#;
    h.a = 4;
    assert 'CWAL_RC_ALREADY_EXISTS'
        === catch{h.takeProperties(h,0)}.codeString();
    assert 2 === h.#;
    h.takeProperties(h,-1);
    assert 4 === h.a;
    assert 3 === h#'a';
    assert 2 === h.#;
}

assert 999 === scope {
    /*
      testing fix:

      https://fossil.wanderinghorse.net/r/cwal/info/5041ab1deee33194

      If it's broken, this will crash if built in debug mode,
      triggering a cwal-level assertion, possibly a different one
      depending on the type of the scope's result value.
    */
    {#a: 999}#'a'
};

scope {
    /** 2020-02-06: dot-length operator (lhs.#) now, on hashes,
        resolves to the number of hash entries.

        2021-07-24: that now seems like a bug.
    */
    assert 3 === {# a:1, b:1, c:1}.#;
}
scope {
    /* 2020-02-18: {#x:=y} assigns x as a const hash entry */
    const h = {# a:1, b:=2};
    assert 1 === h#'a';
    assert 2 === h#'b';
    assert 3 === h.insert('a',3);
    assert 'CWAL_RC_CONST_VIOLATION' === catch h.insert('b',1).codeString();
    assert 2 === h.#;
    assert 2 === foreach(#h=>k,v) 'b'===k && break v;
}

scope {
    /** 2020-02-20: inline expansion of object properties into an
        object literal, similar to JS's {...otherObj}. This is
        currently explicitly disallowed for hash literals because of
        potential semantic ambiguities in handling of hash vs. object
        properties.
    */
    const ex = catch{ {#@{a:1}} };
    assert 'CWAL_RC_TYPE' === ex.codeString();
    assert ex.message.indexOf('hash literal') > 0;
}

scope {
    /* 2021-07-09: Ensure that cwal_prop_key_can() prohibits certain
       property key types... */
    const h = {#};
    assert 'CWAL_RC_TYPE' === catch{
        h.insert(new s2.Buffer(),1);
    }.codeString();
    assert 'CWAL_RC_TYPE' === catch{
        h.insert([#], 1);
    }.codeString();
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<








































































































































































































































































































































































































































Deleted bindings/s2/unit/650-000-tuple.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107

scope {
    // Bugfix 20171111: disallow trailing semicolon in tuple value expressions:
    assert catch{[#1,2;]}.message.indexOf('semicolon')>0;
    assert catch{[#1;,2]}.message.indexOf('semicolon')>0;
}

scope {
    const T = s2.Tuple;
    var t0 = new T(0);
    assert 'tuple' === typename t0;
    assert typeinfo(istuple t0);
    assert !typeinfo(istuple T);

    assert t0 === new T(0) /* same instance! */;
    assert 0 === t0.length();
    assert !t0.#;
    var t1 = new T(1){
        this.0 = 'zero';
    };
    assert 1 == t1.length();
    assert 1 === t1.#;
    assert 'zero' === t1.0;
    assert t1 != t0;
    assert t1 === new T(1){ this.0 = 'zero' };
    assert t1 ==/*overloaded*/ new T(1){ this.0 = 'zero' };
    assert 'CWAL_RC_TYPE' === catch{t1==0}.codeString();
    assert 'CWAL_RC_RANGE' === catch{t1.3}.codeString();
    var t2 = new T(2){this.0 = -1; this.1 = -2};

    /* the comparison ops all disallow a non-tuple RHS */;
    assert t1 < t2;
    assert t2 > t1;
    assert t2 != t1;
    assert t2 !== t1;
    assert "[-1, -2]" === t2.toJSONString(0);
    assert "[-1, -2]" === t2.toString();
    assert 'CWAL_RC_TYPE' == catch {t1<1}.codeString();
    assert 'CWAL_RC_RANGE' == catch {new T(1<<16)}.codeString()
    /* length currently limited to 16 bits (64k) */
    ;
    assert catch {t1>1}.message.indexOf('annot compare')>0;

    var i = 0;
    foreach(t2 => v){assert v<0; ++i};
    assert t2.#===i;
    i = 0;
    assert typeinfo(mayiterate t2);
    assert !typeinfo(mayiterateprops t2);
    assert typeinfo(mayiteratelist t2);
    foreach(t2 => ndx,v){
        assert typeinfo(mayiteratelist t2) /* allowed as of 20191211 */; 
        assert ndx>=0;
        assert v<0;
        ++i;
    };
    assert typeinfo(mayiterate t2);
    assert !typeinfo(mayiterateprops t2);
    assert typeinfo(mayiteratelist t2);
    assert t2.length()===i;
    t2.1 = t1;
    t1.0 = t2;

    assert typeinfo(mayiteratelist [#]);
    
    var t3 = new T([1,2,3]);
    assert 3 === t3.length();
    assert 3 === t3[2];

    t3 = new T(t2);
    assert 2 === t3.length();
    foreach(t3=>i,v) assert t2[i] === v;

    assert 0 === catch {new T(0,1)}.message.indexOf("Expecting");

    // Tuple literals...
    var tL = [#1, 2, [#3, 4, 5]];
    assert typeinfo(istuple tL);
    assert typeinfo(istuple tL.2);
    assert !typeinfo(istuple tL.1);
    assert 5 === tL.2[2];
    assert '1.2.[3, 4, 5]' === tL.join('.') /* curious but true. */;

    // Tuples are treated like arrays for @rray expansion:
    var a = [@[#1,2,3]];
    assert 3 === a.length();
    assert 3 === a.2;

    assert catch {new T(1,2)}.line > 0 /* make sure 'new' decorates C-thrown exceptions */;


    {x:t1}.x; // let it propagate out for lifetime checking.
    // ^^^ reminder to self: t1[0] has a circular refererence, so t1
    // will have a refcount>0 after it leaves this scope. It will be
    // up to vacuuming to clean it up.
}

scope {
    /* More comparisons... */
    const t1 = [#1,2,3],
          t2 = [#3,2,1];
    assert t1 < t2;
    assert t2 > t1;
    assert t2 === [#3.0,2.0,1.0] /* === strictness applies only to the tuples, not their contents. */;
    assert t2 < [#3,2,1.01];
    assert t1 > [#1];
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






















































































































































































































Deleted bindings/s2/unit/700-000-foreach.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155

scope {

    var i = 0;
    foreach({a:1,b:2} => k){
        ++i;
        assert 'a'===k || 'b'===k;
    }
    assert 2 === i;
    var obj = {a:1,b:2};
    i = 0;
    foreach(obj => k,v){
        assert obj[k] === v;
        if(2===++i){
            assert 'CWAL_RC_IS_VISITING'===catch{
                obj[k] = 1 /* assignment fails while iterating
                              (limitation of cwal's properties
                              model).*/
            }.codeString();
        }
    }
    assert 2 === i;

    i = var j = 0;
    foreach(@[1,2,3] => ndx,v) i+=v, j+=ndx;
    assert 6 === i;
    assert 3 === j;
    unset j;

    i = 0;
    foreach(#{#a:1, b:2} => k,v){
        assert 'a'===k || 'b'===k;
        assert 'a'===k ? 1===v : 2===v;
        ++i;
    }
    assert 2 === i;

    i = 0;
    foreach({}.prototype => k,v) ++i;
    assert i > 10;

    assert undefined === foreach(@[]=>k) assert 0;
    assert undefined === foreach({}=>k) assert 0;
    assert undefined === foreach({#}=>k) assert 0;

    // Slightly special behaviour for enums, for consistency
    // in handling them regardless of their property storage type...
    // We "just happen to know" that enums switch from objects to hashes
    // if they get "big enough" (counting their reverse mappings)...
    // 2020-02-21: enums are now always hashes, to eliminate special-case
    // handling such as this.

    var myEnum = enum {a,b,c,d,e};
    i = 0;
    foreach(myEnum=>k){
        assert typeinfo(isstring k);
        assert typeinfo(isunique myEnum[k]);
        ++i;
    }
    assert 5 === i;
    assert 5 === myEnum.#;
    i = 0;
    foreach(myEnum=>k,v){
        assert typeinfo(isstring k);
        assert typeinfo(isunique v);
        assert myEnum[k] === v;
        assert myEnum[v] === k;
        ++i;
    }
    assert i === 5;
    unset myEnum;

    assert 3 === foreach(@[1,2,3] => v){
        v<3 ? continue : break v;
        throw "impossible";
    };
    assert 2 === foreach(@[1,2,3]=>v) v%2 || break v;

    /* Empty arrays and tuples must be a no-op: */
    i = 0;
    foreach(@[]=>i) assert false /*loop body is never eval'd*/;
    assert 0===i;
    foreach([#]=>i) assert false /*loop body is never eval'd*/;
    assert 0===i;

}

scope {
    /* foreach() in a ternary must not generate a syntax error... */
    assert 1 === 0 ? foreach(@[0]=>v) v : 1;
    assert 0 === 1 ? foreach(@[0]=>v) break v : 1;
}

scope {
    var a = [1], i =0;
    a[4] = 1;
    foreach( @a => v ) ++i;
    assert 5 === i /* array visitation now includes C-level NULL entries (as the undefined value). */;
}

scope { // foreach(string=>...)
    var n = 0;
    foreach('© © '=>i,v){
        assert v === i%2 ? ' ' : '©';
        ++n;
    }
    assert 4 === n;

    n = 0;
    var x = 'にちは';
    assert x[2] === foreach(x=>i,v){
        ++n;
        0===i && assert 'に'===v;
        1===i && assert 'ち'===v;
        2===i && break v;
        assert x[i] === v;
    };
    unset x;
    assert 3 === n;

    /* We have one slightly different code path (opimization) for ASCII strings: */
    n = 0;
    foreach('abcd'=>i,v){
        0===i && assert 'a' === v;
        1===i && assert 'b' === v;
        2===i && assert 'c' === v;
        3===i && assert 'd' === v;
        ++n;
    }
    assert 4 === n;

    /* For strings, if no key is provided we iterate over the values.
       Contrast with objects, where we iterate over keys in that case. */
    n = 0;
    foreach('にちは'=>v){
        assert 'に' === v || 'ち' === v || 'は' === v;
        ++n;
    }
    assert 3 === n;

    /* Empty strings must be a no-op: */
    n = 0;
    foreach(''=>i) assert false /*loop body is never eval'd*/;
    assert 0===n;
}

scope {
    /* 20181119: allow a break in the foreach operand expression... */

    assert 1 === foreach((0|||break 1)=>k){...};
    assert -1 === foreach(scope {break -1}=>k){...};
    assert catch { /* break from inside a call() must still fail */
        foreach(proc(){break}()=>k){...}
    }.message.indexOf("'break'") > 0;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






















































































































































































































































































































Deleted bindings/s2/unit/800-000-exception.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
const oldStackTrace = pragma(exception-stacktrace true);
/**
   Exceptions are implicitly tested throughout the earlier unit tests,
   but on 20191228 a new trick was discovered which required making a
   minor backwards-incompatible change (albeit a fix) and prompted the
   addition of a new feature, so...
*/

scope {
    /* 20191228: if the exception keyword is followed by anything
       other than a '(', it resolves to the exception prototype. That
       makes it tempting to add similar keywords for "object" and
       "array" and such, but that's a deep, dark rabbit hole.

       This feature was added primarily as an inexpensive way to get
       at the exception prototype from script code, noting that
       exception(0).prototype captures the exception's source code
       location and stack trace, so is not cheap.
    */
    const e = exception(0);
    assert e.prototype === exception;
    assert e.codeString === exception.codeString;
    assert e.prototype === eval{exception} /* "virtual EOF" check */;
}

scope {
    // Custom exception type.
    const MyEx = {
        prototype: exception /* for inherited exception method(s) */,
        __typename: 'MyEx', // optional
        __new: proc(msg = __FLC){ // constructor
            /* Some prototype gymnastics are needed... */
            const p = this.prototype /* === MyEx */,
                  x = this.prototype = exception(msg)
            /* captures stack trace ^^^^, but now "this"
               no longer inherits MyEx, so... */;
            x.prototype = p
            /* ^^^^ without this part, "this" will inherit exception but NOT
               MyEx. */;
            /**
               Optional (but arguable) cosmetic improvement:
               x.line/column/script refer to this constructor function,
               which may be a bit confusing in practice. We can instead
               "steal" those from one level up in the stack trace (which
               may, of course, also be "differently confusing" in practice)...
            */
            if(const s = x.stackTrace.0){
                x.line = s.line;
                x.column = s.column;
                x.script = s.script;
            }
        }
    };

    assert undefined === MyEx.line;

    const check = proc(x){
        assert x inherits MyEx;
        assert !typeinfo(isexception x) /* because x is not "directly" an exception, but: */;
        assert typeinfo(hasexception x) /* because x inherits an exception */;
        assert "MyEx" === typeinfo(name x);
        assert typeinfo(isarray x.stackTrace) /* actually, it's inherited: */;
        assert x.prototype.stackTrace === x.stackTrace;
        //print(__FLC,'x.stackTrace =',x.stackTrace);
        assert x.stackTrace.0.script === __FILE;
        assert "blah!" === x.message /* derived from x.prototype */;
        assert typeinfo(isinteger x.code) /* derived from x.prototype. */;
        assert typeinfo(isinteger x.line) /* derived from x.prototype. */;
        //print(__FLC,thisLine, x.line);
        assert x.line > thisLine /* because of our finagling in the MyEx ctor */;
        assert typeinfo(isinteger x.column) /* derived from x.prototype. */;
        assert 'CWAL_RC_EXCEPTION' === x.codeString();
        assert !x.hasOwnProperty('script');
        assert x.script === __FILE;
        //print(__FLC, x.prototype);
    } using {
        thisLine: __LINE
    };

    // And now...
    check(catch throw new MyEx("blah!"));
    check(new MyEx("blah!"));
}

scope {
    // Make sure that script-location info is captured for this case...
    const e = catch proc(){throw "hi"}()
                 /* ^^^ need a level of indirection to get the
                    stackTrace property. stackTrace is not included if
                    it's only 1 level deep because it would simply
                    duplicate the line/col/script position set in the
                    other properties.
                 */;
    assert typeinfo(isinteger e.line);
    assert typeinfo(isinteger e.column);
    assert typeinfo(isarray e.stackTrace);
    assert e.stackTrace.0.script === __FILE;
    assert typeinfo(isstring e.script);
    assert "hi" === e.message;
    /**
       There's another use case we can't(?) test from here: if a
       cwal_exception is created in native code and passed to
       s2_throw_value(), it "should" also get script info if a script
       is currently active. Testing that requires adding a native
       function binding solely to test it with, so we'll punt on that
       very hypothetical problem for now. Native-side exceptions which
       pass through a script-side function call get decorated with
       that info if they don't already have it.
    */
}

scope {
    /* Test that s2_cstr_to_rc() is working, i.e. that both implementations
       of the hashing routine produce identical results. */
    foreach(@[
        // 'CWAL_RC_OK', this won't work work in this context
        'CWAL_RC_ERROR',
        // 'CWAL_RC_OOM', // see below!
        'CWAL_RC_FATAL',
        'CWAL_RC_CONTINUE',
        'CWAL_RC_BREAK',
        'CWAL_RC_RETURN',
        'CWAL_RC_EXIT',
        'CWAL_RC_EXCEPTION',
        'CWAL_RC_ASSERT',
        'CWAL_RC_MISUSE',
        'CWAL_RC_NOT_FOUND',
        'CWAL_RC_ALREADY_EXISTS',
        'CWAL_RC_RANGE',
        'CWAL_RC_TYPE',
        'CWAL_RC_UNSUPPORTED',
        'CWAL_RC_ACCESS',
        'CWAL_RC_IS_VISITING',
        'CWAL_RC_IS_VISITING_LIST',
        'CWAL_RC_DISALLOW_NEW_PROPERTIES',
        'CWAL_RC_DISALLOW_PROP_SET',
        'CWAL_RC_DISALLOW_PROTOTYPE_SET',
        'CWAL_RC_CONST_VIOLATION',
        'CWAL_RC_LOCKED',
        'CWAL_RC_CYCLES_DETECTED',
        'CWAL_RC_DESTRUCTION_RUNNING',
        'CWAL_RC_FINALIZED',
        'CWAL_RC_HAS_REFERENCES',
        'CWAL_RC_INTERRUPTED',
        'CWAL_RC_CANCELLED',
        'CWAL_RC_IO',
        'CWAL_RC_CANNOT_HAPPEN',
        'CWAL_RC_JSON_INVALID_CHAR',
        'CWAL_RC_JSON_INVALID_KEYWORD',
        'CWAL_RC_JSON_INVALID_ESCAPE_SEQUENCE',
        'CWAL_RC_JSON_INVALID_UNICODE_SEQUENCE',
        'CWAL_RC_JSON_INVALID_NUMBER',
        'CWAL_RC_JSON_NESTING_DEPTH_REACHED',
        'CWAL_RC_JSON_UNBALANCED_COLLECTION',
        'CWAL_RC_JSON_EXPECTED_KEY',
        'CWAL_RC_JSON_EXPECTED_COLON',
        'CWAL_SCR_CANNOT_CONSUME',
        'CWAL_SCR_INVALID_OP',
        'CWAL_SCR_UNKNOWN_IDENTIFIER',
        'CWAL_SCR_CALL_OF_NON_FUNCTION',
        'CWAL_SCR_MISMATCHED_BRACE',
        'CWAL_SCR_MISSING_SEPARATOR',
        'CWAL_SCR_UNEXPECTED_TOKEN',
        'CWAL_SCR_UNEXPECTED_EOF',
        'CWAL_SCR_DIV_BY_ZERO',
        'CWAL_SCR_SYNTAX',
        'CWAL_SCR_EOF',
        'CWAL_SCR_TOO_MANY_ARGUMENTS',
        'CWAL_SCR_EXPECTING_IDENTIFIER',
        'S2_RC_END_EACH_ITERATION',
        'S2_RC_TOSS'
    ]=>k){
        if(exception(k,0).codeString()!==k)
            throw "codeString check failed for "+k;
        assert 1 /* to count the above check */;
    }
    affirm 'CWAL_RC_EXCEPTION' === exception('CWAL_RC_OOM',1).codeString()
    /* this gets changed to CWAL_RC_EXCEPTION to avoid an erroneous OOM! */;

}

pragma(exception-stacktrace oldStackTrace);
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<












































































































































































































































































































































































Deleted bindings/s2/unit/825-000-define.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
assert typeinfo(isfunction define);
const o = {a:1};
assert o === define('__OO',o);
assert o === __OO;
assert 'CWAL_RC_ALREADY_EXISTS' ===
    catch define('__OO',1).codeString();
assert 'CWAL_RC_ACCESS' ===
    catch define('if',1).codeString();
assert 'CWAL_SCR_SYNTAX' ===
    catch define('non-identifier',1).codeString();
assert 'CWAL_RC_ALREADY_EXISTS' ===
    catch {var __OO = 1}.codeString();
assert 1 === (1 ||| defined(wrong)) /*behaves in skip-mode*/;
assert 'CWAL_RC_UNSUPPORTED' ===
    catch define('x',undefined).codeString();
assert 'CWAL_RC_RANGE' ===catch define('','').codeString();
assert 'CWAL_RC_RANGE' ===
    catch define(
        'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
        1
    ).codeString() /* name too long */;
define('__z',1);
define('__zz',2);
define('__zzz',3);
assert o === __OO;
assert 3 === __zzz;
assert 2 === __zz;
assert 1 === __z;
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
























































Deleted bindings/s2/unit/850-000-pragma.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/**
   20191230: pragma(...) was introduced to query/modify engine-level
   state.
*/
const oldStackTrace = pragma(exception-stacktrace true);

scope {
    const E = proc(){return exception(0)};
    assert pragma(exception-stacktrace);
    var e = E();
    assert typeinfo(isarray e.stackTrace);
    assert pragma(exception-stacktrace 5);
    assert true === pragma(exception-stacktrace);
    assert pragma(exception-stacktrace null);
    assert false === pragma(exception-stacktrace);
    e = E();
    assert undefined === e.stackTrace;
    assert false === pragma(exception-stacktrace true) /* result === old value */;
    e = E();
    assert typeinfo(isarray e.stackTrace);
}

pragma(exception-stacktrace false);

scope {
    assert 0 === pragma(refcount "")/*builtin value*/;
    var x;
    assert 0 === pragma(refcount x)/*undefined value (builtin)*/;
    x = 1000;
    assert 0 < pragma(refcount x)
    /* What?... the refcount is 2 from the shell and 3 here. */;
}

scope {
    assert typeinfo(isinteger pragma(sweep-interval));
    assert typeinfo(isinteger pragma(vacuum-interval));

    var i = pragma(sweep-interval);
    assert i > 0;
    pragma(sweep-interval 99);
    assert 99 === pragma(sweep-interval);
    pragma(sweep-interval i);
    assert i === pragma(sweep-interval);

    i = pragma(vacuum-interval);
    assert i > 0;
    pragma(vacuum-interval 99);
    assert 99 === pragma(vacuum-interval);
    pragma(vacuum-interval i);
    assert i === pragma(vacuum-interval);

    assert 'CWAL_RC_RANGE'===catch pragma(sweep-interval -1).codeString();
    assert 'CWAL_RC_RANGE'===catch pragma(vacuum-interval -1).codeString();
}

scope {
    const i = pragma(trace-sweep);
    assert typeinfo(isinteger i);
    var t = pragma(trace-sweep 2);
    assert i === t;
    assert 2 === pragma(trace-sweep 0);
    assert 0 === pragma(trace-sweep i);
}

scope {
    const i = pragma(trace-assert);
    assert typeinfo(isinteger i);
    const t = pragma(trace-assert 1);
    // be careful with the order, to prevent
    // generating extraneous output...
    assert 1 === pragma(trace-assert 0);
    assert i === t;
    assert 0 === pragma(trace-assert i);
}

/* Test for pragma(trace-token-stack) intentionally elided because it
   generates output to stdout. It uses the exact same code as several
   other trace-xxx pragmas, though.
*/

pragma(exception-stacktrace oldStackTrace);
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


































































































































































Deleted bindings/s2/unit/900-000-pathfinder.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119

scope {
    assert s2.PathFinder;
    const pf = new s2.PathFinder();
    assert 'PathFinder' === typeinfo(name pf);
    assert pf inherits s2.PathFinder;
    pf.prefix = ['/etc', '/bin', '/usr/bin'];
    assert '/etc/hosts' === pf.search('hosts');
    assert pf.search('ls').indexOf('/ls') >= 0;
    assert undefined === pf.search('ls',-1);
    assert undefined === pf.search('no-such-file-we-hope');

    pf.prefix = [/*'',*/ 'unit','.'];
    pf.suffix = [/*'',*/ '.s2'];
    const emptyScript = '000-000-0empty';
    assert pf.search(emptyScript);
    assert undefined === pf.search('no-such-script');
    assert typeinfo(isstring pf.search(__FILE));
    assert catch{
        s2.PathFinder.search('anything')
    }.message.indexOf('is not a PathFinder') > 0;

    // Testing special-case handling of pf.search() args:
    pf.'operator->'=pf.search;
    assert typeinfo(isstring pf->'000-000-0empty');
    assert (pf->emptyScript).indexOf('.s2') > 0;

    pf.prefix = ['/'];
    pf.suffix = [''];
    assert undefined === pf->'etc';
    assert undefined === pf.search('etc');
    assert typeinfo(isstring pf.search('etc',1));
    assert typeinfo(isstring pf.search('etc',-1));
}

scope {
    /*
      20200118 bugfix: previously, if the search path had no entries
      then it was not possible to find a match even if the
      searched-for name was a precise match by itself or in
      combination with any of the configured extensions. Now the
      extension list is considered even if the directory list is
      empty.
    */
    const pf = new s2.PathFinder();
    assert pf.search(__FILE);
    pf.suffix = ['.s2'];
    assert __FILE === pf.search(__FILE.split('.').shift());
}

scope {
    /*
      20200118: added PathFinder.tokenizePath()

      These tests will fail on Windows builds because those builds
      only support ';' as the path delimiter. How best to deal with
      that is as-yet-undecided (and low priority: to the best of my
      knowledge, s2 has never been built on Windows).
    */
    const F = s2.PathFinder;
    var a, path = ":/a/b/c;/d/e/f:;:/g/h/i/.;;";
    a = F.tokenizePath(path);
    assert 3 === a.#;
    assert '/a/b/c' === a.0;
    assert '/d/e/f' === a.1;
    assert '/g/h/i/.' === a.2;
    assert a === F.tokenizePath(path, a);
    assert 6 === a.# /* appended entries to a */;
    for(var i = 0; i < 3; ++i) assert a[i]===a[i+3];
    assert 0 === F.tokenizePath('').#;
    assert 'CWAL_RC_MISUSE' === catch F.tokenizePath().codeString();
    assert 'CWAL_RC_MISUSE' === catch F.tokenizePath(0/*non-string*/).codeString();
    assert 'CWAL_RC_MISUSE' === catch F.tokenizePath('',0/*non-array*/).codeString();
}

if(const dir = './unit';
   s2.PathFinder.fileIsAccessible(dir) ){
    /* 20200119: verify that Unique-type unwrapping works as
       documented for PathFinder.search(). */
    const e = enum {
        dirOnly: -1, fileOnly: 0, both: 1
    };
    const p = new s2.PathFinder([dir], ['.s2']);
    assert p.search(dir, e.dirOnly);
    assert p.search(dir, e.both);
    assert !p.search(dir, e.fileOnly);
    const f = '000-000-0empty';
    assert !p.search(f, e.dirOnly);
    assert p.search(f, e.both);
    assert p.search(f, e.fileOnly);
}

scope {
    /*
      20200120: test support for passing strings to the constructor.
    */
    var p = new s2.PathFinder('a:b;c');
    assert !p.suffix /* was not initialized by the ctor */;
    assert 3 === p.prefix.#;
    assert 'c' === p.prefix.2;
    assert 'b' === p.prefix.1;
    assert 'a' === p.prefix.0;

    p = new s2.PathFinder(null,'a:b;c');
    assert !p.prefix /* null/undefined ctor args are silently skipped */;
    assert 3 === p.suffix.#;
    assert 'c' === p.suffix.2;

    p = new s2.PathFinder(undefined,null);
    assert !p.prefix;
    assert !p.suffix;

    p = new s2.PathFinder('','');
    assert 0 === p.prefix.#;
    assert 0 === p.suffix.#;
    
    assert 'CWAL_RC_TYPE'===catch{new s2.PathFinder(1)}
        .codeString() /* non-string/array/null/undefined argument */;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<














































































































































































































































Deleted bindings/s2/unit/900-001-ob.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// TODO: more output buffering tests, when i'm not so tired.
scope {
  assert 'object' === typename s2.ob;
  const ob = s2.ob;
  assert 0 === ob.level();
  assert ob === ob.push();
  print('buffered');
  var b = ob.pop(1);
  assert 0 === ob.level();
  assert b inherits s2.Buffer;
  assert b.length()>0;
  // Reminder to self: if assertion tracing is on, assertions will generate
  // output to our buffer, making exact content checks here impractical.
  // But we can do...
  assert b.toString().indexOf('buffered') >= 0;
  //print("Buffered",b.length(),"bytes.");
}

scope{
  const ob = s2.ob;
  const out = print;
  assert 1 === ob.push().level();
      out("This will be flushed to stdout.");
      ob.flush();
      out("level 1");
      var v1 = ob.takeString();
      assert ob === ob.push(100);
          out("This will be flushed to level 1.");
          ob.flush();
          out("level 2");
          var v2 = ob.takeString();
      assert ob === ob.pop();
      var v1b = ob.takeString();
      out("discarded");
      //ob.clear()// not needed b/c pop() will do this
  ob.pop();
  assert v1 === 'level 1\n';
  assert v2 === 'level 2\n';
  assert v1b.indexOf('This will be flushed to level 1.\n')>=0
    /* use indexOf() insted of === comparison because s2sh -A (assert
       tracing) breaks a direct comparison by injecting assertion
       tracing output into the buffer. */;
}

scope {
    const ob = s2.ob;
    const fHi = proc(){s2out<<"hi"};
    var v = ob.capture(fHi);
    assert "hi" === v;
    v = ob.capture("s2out<<'hi again'");
    assert "hi again" === v;
    v = ob.capture("s2out<<'hi'", 1);
    assert typeinfo(isbuffer v);
    assert "hi" === v.takeString();
    v = ob.capture(fHi, 0);
    assert undefined === v;
    const level = ob.level();
    v = ob.capture(proc(){
        s2out << "hi";
        ob.push();
        s2out << " again";
        // intentionally leaving extra entry: capture() will pop it.
    });
    assert ob.level() === level;
    assert "hi again" === v;

    const b = new s2.Buffer(/*reserving memory will bypass
                              an allocation optimization*/);
    assert b === ob.capture(eval=>{
        s2out << "hi";
        ob.push();
        s2out << " again";
    }, b);
    assert "hi again" === b.takeString();

    assert 'CWAL_RC_MISUSE' === catch ob.capture().codeString();
    assert 'CWAL_RC_MISUSE' === catch ob.capture(b).codeString()
    /* Should arguably be CWAL_RC_TYPE, but the args validation isn't
       that detailed. */;
    
    // We cannot test the too-many-ob.pop()-calls case here without
    // screwing up downstream tests.
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






































































































































































Deleted bindings/s2/unit/900-002-switch.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const doSwitch = proc(switch, key, evalStrings = false){
  affirm 'object' === typeinfo(name switch);
  affirm undefined !== key;
  var v = switch.(key), tv = typeinfo(name v);
  ('undefined'===tv) && (tv = typeinfo(name (v = switch.default)));
  v || return v;
  ('function' === tv) && return v.call(switch);
  (evalStrings && ('string'===tv)) && return eval -> v;
  return v;
};

scope {
  const switch = {
    1: 'foo',
    2: 'bar',
    default: proc(){ return 'baz' }
  };

  assert 'foo' === doSwitch(switch,1);
  assert 'bar' === doSwitch(switch,2);
  assert 'baz' === doSwitch(switch,99);
}

scope {
  const sw2 = {
    1: '3+3',
    bar: <<<EOF // remember, this is a string
      var x = 3;
      x * 4; EOF,
    ref:proc(){
      return doSwitch(this,1,true)
    }
  };

  assert 6 === doSwitch(sw2,1,true);
  assert 12 === doSwitch(sw2,'bar',true);
  assert 6 === doSwitch(sw2,'ref',true);
  assert undefined === doSwitch(sw2,'baz',true);
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<














































































Deleted bindings/s2/unit/900-003-fs.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
(const FS = s2.fs) || return;


assert FS.dirSeparator;
assert FS.dirIsAccessible('.');
assert !FS.dirIsAccessible('./nope');
assert FS.fileIsAccessible(__FILE);

if(typeinfo(isfunction (const G = FS.getcwd))){
    var d = G(), d2 = G(true), DS = FS.dirSeparator;
    assert d === G(false);
    assert d !== d2;
    assert d2.length() === d.length() + 1;
    assert d === d.split(DS).join(DS);
    assert DS === d2.charAt(d2.length()-1);
    assert d2 === d2.split(DS).join(DS);


    if(typeinfo(isfunction (const C = FS.chdir))){
        const cwd = G();
        assert catch C();
        assert undefined === C('/');
        assert '/' === G();
        assert undefined === C(cwd);
    }
}

if(typeinfo(isfunction (const S = FS.stat))){
    assert true === S('.',undefined) /* we can always stat(2) the current dir, right? */;
    assert false === S('...',undefined) /* Doesn't throw on stat(2) failure */;
    assert catch S('...') /* Throws if stat(2) fails. */;
    assert catch S(0) /* Throws if argv[0] is not a string or buffer */;
    var o = S('.');
    assert typeinfo(isobject o);
    assert typeinfo(isinteger o.ctime);
    assert typeinfo(isinteger o.mtime);
    assert typeinfo(isinteger o.size);
    assert typeinfo(isinteger o.perm);
    assert 'dir' === o.type;
}

;;

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






















































































Deleted bindings/s2/unit/900-004-glob.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
if(!typeinfo(isfunction s2.glob)){
    print("no s2.glob() function. Skipping tests.");
    /* reminder: normally we could 'return' from here,
       but that would premanturely end one the
       unit test which combines all unit tests files
       into a single file before execution. */
}else{
    var g = s2.glob;
    /* glob matching policies (3rd glob() param):

       <0 = (default) wildcard-style (case sensitive)

       0 = SQL LIKE style (case insensitive)

       >0 = SQL LIKE style (case sensitive)
    */
    assert typeinfo(islocal g)
        /* for symmetry with a test much further down... */;
    // Must throw if given any non-string arguments:
    assert catch g(undefined,"");
    assert catch g("",undefined);
    // Throw if glob is empty (input haystack be empty):
    assert 0 === catch g("","").message.indexOf('Glob string');

    assert g("*", "");
    assert g("%", "",0);
    assert g("%", "",1);

    assert g("ab*", "abcd");
    assert g("ab*", "abcd", -1 /* === default */);
    assert g("[a]bc", "abc");
    assert !g("[a]bc", "Abc");
    assert g("a[bB]c", "aBc");

    assert g("ab%d", "abcd", 0);
    assert g("ab%d", "abcd", 1);
    assert g("ab%d", "AbcD", 0);
    assert !g("ab%d", "AbcD", 1);

    assert g("_b_d", "AbcD", 0);
    assert !g("A__d", "Abcd");
    assert g("A__d", "Abcd", 1);
    assert !g("A__d", "AbcD", 1);

    assert g('*☺Z', "abc☺Z");
    assert g('___☺Z', "abc☺Z", 0);
    assert g('___☺Z', "abc☺Z", 1);

    assert g('%んにち%', 'abcんにちdef',0);
    assert g('%んにち%', 'abcんにちdef',1);

    assert g('*ん?ち*', 'abcんにちdef');
    assert g('%ん_ち%', 'abcんにちdef',0);
    assert g('%ん_ち%', 'abcんにちdef',1);

    assert g('*んにち*', 'abcんにちdef');
    assert g('%cんにちd%', 'abcんにちdef',0);
    assert !g('%CんにちD%', 'abcんにちdef',1);

    const sP = "".prototype;
    const oldOp1 = sP.'operator=~', oldOp2 = sP.'operator!~';

    sP.'operator=~' = proc(glob) using (g) { return g(glob, this) };
    sP.'operator!~' = proc(glob) { return !this.'operator=~'(glob) };
    unset g;
    assert !typeinfo(islocal g)
    /* just proving that g is no longer in scope for the following calls
       operator calls. */;

    assert 'abcんにちdef' =~ '*んにち*';
    assert "abc☺Z" =~ '*☺Z';
    assert "Abc" !~ "[a]bc";
    
    sP.'operator=~' = oldOp1;
    sP.'operator!~' = oldOp2;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
























































































































































Deleted bindings/s2/unit/910-000-s2sh.s2.

1
2
3
4
5
6
7
8
9
10
11

if(s2.sealObject) {
    const o = {a:1};
    assert 1 === o.a;
    assert 2 === (o.b=2);
    assert o === s2.sealObject(o);
    assert 2 === o.b;
    assert catch {o.b=3}.codeString() === 'CWAL_RC_DISALLOW_PROP_SET';
    assert catch s2.sealObject().codeString() === 'CWAL_RC_MISUSE';
    assert catch s2.sealObject(0).codeString() === 'CWAL_RC_TYPE';
};
<
<
<
<
<
<
<
<
<
<
<






















Deleted bindings/s2/unit/950-000-bughunt.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// this script is for testing thing which have been known to
// trigger/cause cwal-level assertions...

if(1){ // eval-holder: these must not crash anymore...
    (var a = 'x'), (a = a + 'y'), a;
    /** ^^^

        Crashes sometimes. Unfortunately very picky about combination of
        string interning and recycling options, as well as the current
        state of the recyling subsystem. Enabling the "eval holder"
        resolves it (see s2_eval.c's EVAL_USE_HOLDER), and its unclear
        whether (but it seems doubtful that) a solution which doesn't
        involve the eval holder is possible without an overhaul of the
        eval engine to intricately manage refs of all values in the stack
        machine. That would require touching (very carefully) every
        routine which manipulates the stack machine.
    */

    scope {
        (var a = 'x'), (a = a + 'y'), a
        /* crashes other times (without the eval holder) */
        ;;
    }
}

if(1){
    // (Re)discovered and fixed 20180507:
    var a, b;
    a++ ? 1 : 2; b;
    /*
      ==> Unexpected token after postfix operator: (OpIncrPost) ==> (Identifier)

      That triggers after the ternary expr completes, but adding a
      second semicolon after the ternary expr fixes it. Other postfix
      ops also trigger it.
    */
    assert 1 === a;
}

assert 'CWAL_SCR_SYNTAX' === catch{new s2.Buffer( s2. /*trailing dot*/)}.codeString()
/* That used to (until 20171205) trigger an assert in the
   line-counting code because the eval loop wasn't catching the
   trailing dot in that context. */
;
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
























































































Deleted bindings/s2/unit/Makefile.

1
2
all:
	$(MAKE) -C .. unit
<
<




Deleted bindings/s2/unit2/000-000-sanity.s2.

1
2
3
assert 'Fossil' === typename Fossil;
assert 'Db' === typename Fossil.Db;
assert 'Stmt' === typename Fossil.Db.Stmt;
<
<
<






Deleted bindings/s2/unit2/000-075-time.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

scope {
    const tm = Fossil.time;
    var n = tm.now();
    var j = tm.unixToJulian(n);
    //print(n, tm.julianToUnix(j));
    //assert n === tm.julianToUnix(j) /* sometimes fails on rounding errors! Soo.... */;
    0.prototype.'operator=~' = proc(r){
        affirm 'integer' === typename r;
        return (this===r) || (this===(r-1)) || (this===(r+1));
    };
    assert n =~ tm.julianToUnix(j) /* round-trip sometime rounds badly, so we check for a +/-1 match */;
    unset n.prototype.'operator=~' /* avoid polluting downstream script code */;
    assert 19 === tm.julianToISO8601(j).length() /* w/o ms */;
    assert 23 === tm.julianToISO8601(j,true).length() /* w/ ms */;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
































Deleted bindings/s2/unit2/000-100-db.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
scope {
    /* Basic sanity checks for the Db and Stmt bindings. */
    var db = Fossil.Db.open(':memory:');
    assert db inherits Fossil.Db;
    assert 'Db' === typename db;
    //db.eachProperty(print);

    var st = db.prepare("CREATE TABLE t(a,b)");
    assert st inherits Fossil.Db.Stmt;
    assert 'Stmt' === typename st;
    assert undefined === st.step();
    st.finalize();
    assert 0 === catch{st.finalize()}.message.indexOf("'this'");
    st = 0; // will (in this case) clean up the wrapping part of the binding immediately

    assert db === db.execMulti(<<<EOSQL
                 DELETE FROM t;
                 INSERT INTO t VALUES(1,2);
                 INSERT INTO t VALUES(3,4);
                 EOSQL);
    var counter = 0;
    assert db === db.each({
        sql: 'select a a, b b from t', // string or buffer
        mode: 0, // 0===get each row as an object, else as an array
        callback:<<<EOF // string/buffer or function
          // Local vars defined in an each() callback:
          // integer columnCount, integer rowNumber, Array columnNames,
          // Object|Array this.
          counter = rowNumber;
          assert "object"===typename this;
          //'print("row", rowNumber, this)'
        EOF
        // bind: if sql prop contains bound params, then a value,
        // array of values, or an object with named params as keys.
        // See examples below.
    });
    assert 2 === counter;
    counter = 0;
    db.each({
        sql: 'select a a, b b from t',
        callback: proc(){
            counter = rowNumber;
            assert 'array' === typename this;
            assert 2 === this.length();
            //'print("row", rowNumber, this)'
        }
    });
    assert 2 === counter;

    counter = 0;
    db.prepare("select a, b from t").each(proc(){
        affirm 'Stmt'===typeinfo(name this);
        ++counter;
        assert 1===this.get(0)%2;
        assert 0===this.get(1)%2;
        assert catch {this.get(2)}.codeString()==='CWAL_RC_RANGE';
    }).finalize();
    affirm 2 === counter;

    var lastCol;
    counter = 0;
    db.each({
        sql: 'select a a, b b from t where a>:min AND a<$max',
        bind: {':min':1, $max:10} /* note that the ':' resp '$' is mandatory,
                                     but $ is an identifier char and need not
                                     be quoted. */,
        mode: 0, // 0 === fetch each row as an object
        callback: proc(){
            counter = rowNumber;
            assert 'object' === typename this;
            lastCol = this.a;
            //print("row", rowNumber, this)
        }
    });
    assert 1 === counter;
    assert 3 === lastCol;

    st = db.prepare('select a a, b b from t where a>?1 AND a<?2'
                    +' AND a<?2 AND a>?1'/*checking if binding ?N works right*/);
    st.bind([0, 10]);
    counter = 0;
    while(st.step()){++counter}
    assert 2 === counter;
    st.reset();
    st.bind(1, 1).bind(2,5);
    counter = 0;
    lastCol = 0;
    while(var row = st.stepArray()){
        ++counter;
        lastCol = row.1;
    }
    st.finalize();
    assert counter === 1;
    assert lastCol === 4;

    counter = 0;
    db.each({sql:'select * from t order by a',
             callback:'++counter>0 && return false'
            });
    assert 1 === counter;

    assert 42 === catch{
        db.each({sql:'select * from t order by a',
                 callback:'throw 42'})
    }.message;

    counter = 0;
    db.exec("create temp table ttmmpp(a)",
            "insert into ttmmpp values(1),(2),(3)"
           )
        .each({sql:"select a from ttmmpp",
               callback: 'counter += this.0'})
            ;
    assert 6 === counter;

    // Make sure transactions basically work...
    assert 0 === db.transactionState();
    db.begin();
    assert 1 === db.transactionState();
    db.exec("update ttmmpp set a=NULL");
    assert null === db.selectValue('select a from ttmmpp');
    db.begin(); // nested!
    assert 2 === db.transactionState();
    db.exec("update ttmmpp set a=0");
    assert 0 === db.selectValue('select a from ttmmpp');
    db.rollback(); // only queues up the rollback b/c we are nested
    assert -1 === db.transactionState()/* negated value of current transaction level */;
    db.commit(); // actually rolls back b/c of previous rollback() call
    assert 0 === db.transactionState();
    assert 1 === db.selectValues('select a from ttmmpp order by a').0;
    assert 3 === db.selectValue('select a from ttmmpp order by a desc');

    /*
      Alternately (more simply) use db.transaction() to handle the
      begin/commit/rollback() management. Give it a function and it
      will begin(), call the function, and either commit or roll back
      (if the function call throws/propagates an error).
    */
    const rollbackMsg = "Will roll back.";
    var ex = catch db.transaction(proc(){
        // In this callback, "this" is the db instance.
        this.exec("update ttmmpp set a=NULL");
        assert null === this.selectValue('select a from ttmmpp');
        assert 1 === this.transactionState();
        throw rollbackMsg;
    });
    assert 0 === db.transactionState();
    assert ex;
    assert rollbackMsg === ex.message;
    assert 1 === db.selectValue('select a from ttmmpp order by a');
    ex = catch db.transaction(proc(){
        assert 1 === this.transactionState();
        this.exec("update ttmmpp set a=NULL");
    });
    assert !ex;
    assert 0 === db.transactionState();
    assert null === db.selectValue('select a from ttmmpp','default');

    // Once more, this time nested...
    ex = catch db.transaction(proc(){
        this.exec("update ttmmpp set a=1");
        this.transaction(proc(){
            assert 1 === this.selectValue('select a from ttmmpp');
            this.exec("update ttmmpp set a=2");
            assert 2 === db.transactionState();
            // Making sure script-stopper events are not downgraded
            // to exceptions:
            //assert !'just testing assert() propagation';
            //exit 'just testing exit() propagation';
            //fatal 'just testing fatal() propagation';
            throw 42;
        });
        throw "Never reached.";
        this.exec("update ttmmpp set a=3");
    });
    //print(__FLC,"ex =",ex);
    assert 0 === db.transactionState();
    assert ex;
    assert 42 === ex.message;
    assert null === db.selectValue('select a from ttmmpp');


    // A Db is closed automatically when it is GC'd, but we
    // can manually close them if needed:
    db.close();
    assert 0 === catch{db.close()}.message.indexOf("'this'");
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






















































































































































































































































































































































































Deleted bindings/s2/unit2/000-200-context.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
assert 'Fossil' === typename Fossil;
assert 'Db' === typename Fossil.Db;
assert 'Stmt' === typename Fossil.Db.Stmt;
assert 'Context' === typename Fossil.Context;

scope {
    var f = new Fossil.Context({traceSql:false});
    //print('f =',f);
    assert 'Context' === typename f;
    assert f inherits Fossil.Context;
    const DB = f.db;
    assert DB inherits Fossil.Db;
    assert DB.name === 'main' /* this is our "main" db */;
    assert !DB.checkout;
    assert !DB.repo;
    assert !DB.config;

    assert f === f.openCheckout();
    assert DB inherits Fossil.Db;
    assert DB.repo inherits Fossil.Db;
    assert DB.checkout inherits Fossil.Db;

    assert DB.checkout.name = 'ckout';
    assert DB.repo.name === 'repo';
    assert DB.filename === ':memory:'
        || DB.filename === '' /* similar mechanism with different
                                 semantics vis-a-vis temp storage */;
    assert DB.checkout.filename;
    assert DB.filename !== DB.checkout.filename;
    assert DB.repo.filename;
    assert DB.repo.filename !== DB.checkout.filename;

    assert !DB.config;
    f.openConfig();
    assert DB.config inherits Fossil.Db;
    assert DB.config.name === 'cfg';
    assert DB.config.filename;
    if(0){
        print('DB.name =',DB.name);
        print('DB.filename =',DB.filename);
        print('DB.repo.name =',DB.repo.name);
        print('DB.repo.filename =',DB.repo.filename);
        print('DB.checkout.name =',DB.checkout.name);
        print('DB.checkout.filename =',DB.checkout.filename);
        print('DB.config.name =',DB.config.name);
        print('DB.config.filename =',DB.config.filename);
    }
    var counter = 0;
    assert DB === DB.each({
        sql:<<<_SQL
            SELECT mtime FROM event LIMIT 2
        _SQL,
        callback:'++counter'
    });
    assert 2 === counter;
    unset counter;

    /* These UUIDs are only valid for the libfossil tree... */
    assert '99237c3636730f20ed07b227c5092c087aea8b0c' === f.symToUuid('rid:1');
    assert 1 === f.symToRid('99237c3636730f20');

    const trunkUuid = f.symToUuid('trunk');
    assert (var b = f.loadBlob('trunk')) inherits s2.Buffer;
    assert !b.isCompressed();
    assert b.length() > 0;
    assert b.sha1() === trunkUuid;

    b.reset() << "fossil";
    assert b.sha3() === '226558c59bda6533fb0869abd36dec2e8fee1003247b98390b5cb639285c0b79';
    //const trunkZCard = b.toString(b.length()-33, 32);
    //print(b,trunkZCard);

    b = f.artifactDiff('prev', trunkUuid, {
        // all available options (not all combinations make sense and some trump others):
        html: false,
        text: true,
        inline: false,
        sbsWidth: 60,
        contextLines: 5,
        invert: false
    });
    assert b inherits s2.Buffer;
    assert b.length() > 10;
    assert !b.isCompressed();
    unset b;

    assert 'object' === typename (var d =  f.loadManifest('trunk'));
    assert d.uuid === trunkUuid;
    assert d.D > 1.0;
    assert 'array' === typename d.F;
    assert 40 === d.P.0.length();
    //assert d.Z === trunkZCard; // doh: fsl_deck does not expose the Z-card b/c it's only internally generated/used.
    //print(d);
    unset d;
    
//    if(typeinfo(isfunction Fossil.Db.selectValue /* installed by init script, but the
//                                                    valgrind tests don't load that. */)){
//        assert 0 < f.db.config.selectValue("select count(*) from global_config");
//    }

    scope {
        var loginCookie = f.loginCookieName();
        assert 'string' === typename loginCookie;
        assert 0 === loginCookie.indexOf('fossil-');
        assert 23 === loginCookie.length();
    }

    if(1){
        if(0){
            /* Has the same effect as else block below... */
            f.finalize();
            assert !f.db;
        }else{
            assert f === f.close();
            assert !f.db;
            assert undefined === f.finalize() /* explicitly frees all C-level resources (or leave it to GC) */;
        }
        assert 0 === catch{f.openCheckout()}.message.indexOf("'this' is not") /* native was disconnected in finalize() */;
        //f.openCheckout();
        assert 0 === catch{DB.prepare('select 1')}.message.indexOf("'this' is not")
        /* The native DB handle held by DB was (with valgrind's help!) disconnected
           from it when f.close() or f.finalize() were called.
        */;
    }
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


























































































































































































































































Deleted bindings/s2/unit2/Makefile.

1
2
all:
	$(MAKE) -C .. unit
<
<




Deleted bindings/s2/vg.make.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# We keep these in a separate file to avoid a rebuild when changing
# these flags. Damn... some automatic rules foil us here.
########################################################################
# Valgrind sanity checks...
VG := $(call ShakeNMake.CALL.FIND_FILE,valgrind)
ifneq (,$(VG))
VG_REPORT := VG.report.csv
VG_FLAGS ?= --leak-check=full -v --show-reachable=yes --track-origins=yes
#SCRIPT_LIST := $(shell ls -1 test-[0-9]*.s2 | sort)
# Whether or not to collect massif-based stats...
RUN_MASSIF := 1
.PHONY: vg
# Reminder: don't use -A b/c it breaks output buffering tests!
VG_SHELL_FLAGS=--a -S
VG_UNIT_RUN_CMD = ./$(f-s2sh.BIN) $(VG_SHELL_FLAGS)
vg: $(f-s2sh.BIN) $(UNIT_GENERATED)
	@echo "Running: $(VG) $(VG_FLAGS) $(VG_UNIT_RUN_CMD) ..."; \
	export LD_LIBRARY_PATH="$(TOP_SRCDIR):$${LD_LIBRARY_PATH}"; \
	massif_tmp=tmp.massif; \
	for i in $(sort $(UNIT_SCRIPT_LIST)) $(UNIT_GENERATED); do \
		vgout=$$i.vg; \
		xtraargs="-o $$i._out -f $$i"; \
		cmd="$(VG) $(VG_FLAGS) $(VG_UNIT_RUN_CMD) $$xtraargs"; \
		echo -n "**** Valgrinding [$$i]: "; \
		$$cmd 2>&1 | tee $$vgout | grep 'total heap usage' || exit $$?; \
		grep 'ERROR SUMMARY' $$vgout | grep -v ' 0 errors' && echo "See $$vgout!"; \
		vgout=$$i.massif; \
		cmd="$(VG) --tool=massif --time-unit=B --massif-out-file=$$vgout --heap-admin=0  $(VG_UNIT_RUN_CMD) $$xtraargs"; \
		test x1 = x$(RUN_MASSIF) || continue; \
		echo -n "**** Massifing [$$i]: "; \
		$$cmd > $$massif_tmp 2>&1 || exit $$?; \
		echo -n "==> $$vgout Peak RAM: "; \
	    ms_print $$vgout | perl -n -e 'if(m/^(\s+)([KM]B)$$/){my $$x=$$2; $$_=<>; m/^(\d[^^]+)/; print $$1." ".$$x; exit 0;}'; \
		echo; \
	done; \
	rm -f $$massif_tmp
	@test x1 = x$(RUN_MASSIF) || exit 0; \
	echo "Done running through s2 scripts. Collecting stats..."; \
	echo 'script,allocs,frees,totalMemory,peakMemUsage,peakMemUsageUnit' > $(VG_REPORT); \
	for i in $$(ls -1 unit/*.s2.vg unit2/*.s2.vg | sort) $(patsubst %.s2,%.s2.vg,$(UNIT_GENERATED)); do \
		base=$${i%%.vg}; \
		echo -n "$$base,"; \
		grep 'total heap usage' $$i | sed -e 's/,//g' -e 's/^\.\///' | awk '{printf "%d,%d,%d,",$$5,$$7,$$9}'; \
		ms_print $${base}.massif | perl -n -e 'if(m/^(\s+)([KM]B)$$/){my $$x=$$2; $$_=<>; m/^(\d[^^]+)/; print $$1.",".$$x; exit 0;}'; \
		echo; \
	done >> $(VG_REPORT); \
	rm -f unit/ms_print.tmp.* unit2/ms_print.tmp.* ms_print.tmp.*; \
	echo "Stats are in $(VG_REPORT):"; \
	tr ',' '\t' < $(VG_REPORT)

CLEAN_FILES += $(wildcard \
	$(patsubst %.s2,%.s2.vg,$(UNIT_GENERATED)) \
	$(patsubst %,unit/%,*.vg *._out *.massif *.db *~ *.uncompressed *.z *.zip *.2) \
	$(patsubst %,unit2/%,*.vg *._out *.massif *.db *~ *.uncompressed *.z *.zip *.2) \
	)
# 'vg' proxies which tweak various s2/cwal-level optimizations...
#vgp: VG_SHELL_FLAGS+=--p
#vgp: VG_REPORT:=VG.report-p.csv
#vgp: vg
vgr: VG_SHELL_FLAGS+=--R -C -S
vgr: VG_REPORT:=VG.report-r.csv
vgr: vg
vgs: VG_SHELL_FLAGS+=--S -R -C
vgs: VG_REPORT:=VG.report-s.csv
vgs: vg
#vgt: VG_SHELL_FLAGS+=--t
#vgt: VG_REPORT:=VG.report-t.csv
#vgt: vg
vgrs: VG_SHELL_FLAGS+=--R -C --S
vgrs: VG_REPORT:=VG.report-rs.csv
vgrs: vg
vgrsc: VG_SHELL_FLAGS+=--R --C --S
vgrsc: VG_REPORT:=VG.report-rsc.csv
vgrsc: vg
# vgrst: VG_SHELL_FLAGS+=--r --s --t
# vgrst: VG_REPORT:=VG.report-rst.csv
# vgrst: vg
# vgprst: VG_SHELL_FLAGS+=--r --s --t --p
# vgprst: VG_REPORT:=VG.report-prst.csv
# vgprst: vg
VG_REPORT_MEGA := VG.report-mega.csv
CLEAN_FILES += $(VG_REPORT_MEGA)
VG_COMMANDS := \
	$(MAKE) vg \
	&& $(MAKE) vgr \
	&& $(MAKE) vgs \
	&& $(MAKE) vgrs \
	&& $(MAKE) vgrsc

vgall:
	@start=$$(date); \
	echo "Start time: $$start"; \
	$(VG_COMMANDS) || exit; \
	echo "Run time: $$start - $$(date)"; \
	rm -f $(VG_REPORT_MEGA); \
	for i in VG.report*.csv; do \
		test -s $$i || continue; \
		echo $$i | grep t-mega >/dev/null && continue; \
		echo "Report: $$i"; \
		cut -d, -f1,2,4,5 $$i | tr ',' '\t'; \
	done > $(VG_REPORT_MEGA); \
	echo "Consolidated valgrind report is in [$(VG_REPORT_MEGA)]."

endif
#/$(VG)
########################################################################
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




















































































































































































































Changes to compile_flags.txt.

1
2
3


4
5
6
7
8
9
10
11
12
13
14
-std=c89
-I
include


-isystem
/usr/local/include
-isystem
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/12.0.0/include
-isystem
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include
-isystem
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include
-isystem
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks




>
>











1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-std=c89
-I
include
-I
src
-isystem
/usr/local/include
-isystem
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/12.0.0/include
-isystem
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include
-isystem
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include
-isystem
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks

Deleted doc/Doxyfile.in.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
# Doxyfile 1.8.17

# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
#
# All text after a double hash (##) is considered a comment and is placed in
# front of the TAG it is preceding.
#
# All text after a single hash (#) is considered a comment and will be ignored.
# The format is:
# TAG = value [value, ...]
# For lists, items can also be appended using:
# TAG += value [value, ...]
# Values that contain spaces should be placed between quotes (\" \").

#---------------------------------------------------------------------------
# Project related configuration options
#---------------------------------------------------------------------------

# This tag specifies the encoding used for all characters in the configuration
# file that follow. The default is UTF-8 which is also the encoding used for all
# text before the first occurrence of this tag. Doxygen uses libiconv (or the
# iconv built into libc) for the transcoding. See
# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
# The default value is: UTF-8.

DOXYFILE_ENCODING      = UTF-8

# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
# double-quotes, unless you are using Doxywizard) that should identify the
# project for which the documentation is generated. This name is used in the
# title of most generated pages and in a few other places.
# The default value is: My Project.

PROJECT_NAME           = libfossil

# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
# could be handy for archiving the generated documentation or if some version
# control system is used.

PROJECT_NUMBER         =

# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
# quick idea about the purpose of the project. Keep the description short.

PROJECT_BRIEF          =

# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
# in the documentation. The maximum height of the logo should not exceed 55
# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
# the logo to the output directory.

PROJECT_LOGO           =

# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
# into which the generated documentation will be written. If a relative path is
# entered, it will be relative to the location where doxygen was started. If
# left blank the current directory will be used.

OUTPUT_DIRECTORY       =

# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
# directories (in 2 levels) under the output directory of each output format and
# will distribute the generated files over these directories. Enabling this
# option can be useful when feeding doxygen a huge amount of source files, where
# putting all generated files in the same directory would otherwise causes
# performance problems for the file system.
# The default value is: NO.

CREATE_SUBDIRS         = NO

# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
# characters to appear in the names of generated files. If set to NO, non-ASCII
# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
# U+3044.
# The default value is: NO.

ALLOW_UNICODE_NAMES    = NO

# The OUTPUT_LANGUAGE tag is used to specify the language in which all
# documentation generated by doxygen is written. Doxygen will use this
# information to generate all constant output in the proper language.
# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
# Ukrainian and Vietnamese.
# The default value is: English.

OUTPUT_LANGUAGE        = English

# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all
# documentation generated by doxygen is written. Doxygen will use this
# information to generate all generated output in the proper direction.
# Possible values are: None, LTR, RTL and Context.
# The default value is: None.

OUTPUT_TEXT_DIRECTION  = None

# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
# descriptions after the members that are listed in the file and class
# documentation (similar to Javadoc). Set to NO to disable this.
# The default value is: YES.

BRIEF_MEMBER_DESC      = YES

# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
# description of a member or function before the detailed description
#
# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
# brief descriptions will be completely suppressed.
# The default value is: YES.

REPEAT_BRIEF           = YES

# This tag implements a quasi-intelligent brief description abbreviator that is
# used to form the text in various listings. Each string in this list, if found
# as the leading text of the brief description, will be stripped from the text
# and the result, after processing the whole list, is used as the annotated
# text. Otherwise, the brief description is used as-is. If left blank, the
# following values are used ($name is automatically replaced with the name of
# the entity):The $name class, The $name widget, The $name file, is, provides,
# specifies, contains, represents, a, an and the.

ABBREVIATE_BRIEF       =

# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
# doxygen will generate a detailed section even if there is only a brief
# description.
# The default value is: NO.

ALWAYS_DETAILED_SEC    = NO

# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
# inherited members of a class in the documentation of that class as if those
# members were ordinary class members. Constructors, destructors and assignment
# operators of the base classes will not be shown.
# The default value is: NO.

INLINE_INHERITED_MEMB  = NO

# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
# before files name in the file list and in the header files. If set to NO the
# shortest path that makes the file name unique will be used
# The default value is: YES.

FULL_PATH_NAMES        = NO

# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
# Stripping is only done if one of the specified strings matches the left-hand
# part of the path. The tag can be used to show relative paths in the file list.
# If left blank the directory from which doxygen is run is used as the path to
# strip.
#
# Note that you can specify absolute paths here, but also relative paths, which
# will be relative from the directory where doxygen is started.
# This tag requires that the tag FULL_PATH_NAMES is set to YES.

STRIP_FROM_PATH        =

# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
# path mentioned in the documentation of a class, which tells the reader which
# header file to include in order to use a class. If left blank only the name of
# the header file containing the class definition is used. Otherwise one should
# specify the list of include paths that are normally passed to the compiler
# using the -I flag.

STRIP_FROM_INC_PATH    =

# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
# less readable) file names. This can be useful is your file systems doesn't
# support long names like on DOS, Mac, or CD-ROM.
# The default value is: NO.

SHORT_NAMES            = NO

# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
# first line (until the first dot) of a Javadoc-style comment as the brief
# description. If set to NO, the Javadoc-style will behave just like regular Qt-
# style comments (thus requiring an explicit @brief command for a brief
# description.)
# The default value is: NO.

JAVADOC_AUTOBRIEF      = YES

# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
# such as
# /***************
# as being the beginning of a Javadoc-style comment "banner". If set to NO, the
# Javadoc-style will behave just like regular comments and it will not be
# interpreted by doxygen.
# The default value is: NO.

JAVADOC_BANNER         = NO

# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
# line (until the first dot) of a Qt-style comment as the brief description. If
# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
# requiring an explicit \brief command for a brief description.)
# The default value is: NO.

QT_AUTOBRIEF           = NO

# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
# a brief description. This used to be the default behavior. The new default is
# to treat a multi-line C++ comment block as a detailed description. Set this
# tag to YES if you prefer the old behavior instead.
#
# Note that setting this tag to YES also means that rational rose comments are
# not recognized any more.
# The default value is: NO.

MULTILINE_CPP_IS_BRIEF = NO

# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
# documentation from any documented member that it re-implements.
# The default value is: YES.

INHERIT_DOCS           = NO

# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
# page for each member. If set to NO, the documentation of a member will be part
# of the file/class/namespace that contains it.
# The default value is: NO.

SEPARATE_MEMBER_PAGES  = NO

# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
# uses this value to replace tabs by spaces in code fragments.
# Minimum value: 1, maximum value: 16, default value: 4.

TAB_SIZE               = 4

# This tag can be used to specify a number of aliases that act as commands in
# the documentation. An alias has the form:
# name=value
# For example adding
# "sideeffect=@par Side Effects:\n"
# will allow you to put the command \sideeffect (or @sideeffect) in the
# documentation, which will result in a user-defined paragraph with heading
# "Side Effects:". You can put \n's in the value part of an alias to insert
# newlines (in the resulting output). You can put ^^ in the value part of an
# alias to insert a newline as if a physical newline was in the original file.
# When you need a literal { or } or , in the value part of an alias you have to
# escape them by means of a backslash (\), this can lead to conflicts with the
# commands \{ and \} for these it is advised to use the version @{ and @} or use
# a double escape (\\{ and \\})

ALIASES                =

# This tag can be used to specify a number of word-keyword mappings (TCL only).
# A mapping has the form "name=value". For example adding "class=itcl::class"
# will allow you to use the command class in the itcl::class meaning.

TCL_SUBST              =

# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
# only. Doxygen will then generate output that is more tailored for C. For
# instance, some of the names that are used will be different. The list of all
# members will be omitted, etc.
# The default value is: NO.

OPTIMIZE_OUTPUT_FOR_C  = YES

# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
# Python sources only. Doxygen will then generate output that is more tailored
# for that language. For instance, namespaces will be presented as packages,
# qualified scopes will look different, etc.
# The default value is: NO.

OPTIMIZE_OUTPUT_JAVA   = NO

# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
# sources. Doxygen will then generate output that is tailored for Fortran.
# The default value is: NO.

OPTIMIZE_FOR_FORTRAN   = NO

# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
# sources. Doxygen will then generate output that is tailored for VHDL.
# The default value is: NO.

OPTIMIZE_OUTPUT_VHDL   = NO

# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
# sources only. Doxygen will then generate output that is more tailored for that
# language. For instance, namespaces will be presented as modules, types will be
# separated into more groups, etc.
# The default value is: NO.

OPTIMIZE_OUTPUT_SLICE  = NO

# Doxygen selects the parser to use depending on the extension of the files it
# parses. With this tag you can assign which parser to use for a given
# extension. Doxygen has a built-in mapping, but you can override or extend it
# using this tag. The format is ext=language, where ext is a file extension, and
# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice,
# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
# tries to guess whether the code is fixed or free formatted code, this is the
# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat
# .inc files as Fortran files (default is PHP), and .f files as C (default is
# Fortran), use: inc=Fortran f=C.
#
# Note: For files without extension you can use no_extension as a placeholder.
#
# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
# the files are not read by doxygen.

EXTENSION_MAPPING      =

# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
# according to the Markdown format, which allows for more readable
# documentation. See https://daringfireball.net/projects/markdown/ for details.
# The output of markdown processing is further processed by doxygen, so you can
# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
# case of backward compatibilities issues.
# The default value is: YES.

MARKDOWN_SUPPORT       = NO

# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
# to that level are automatically included in the table of contents, even if
# they do not have an id attribute.
# Note: This feature currently applies only to Markdown headings.
# Minimum value: 0, maximum value: 99, default value: 5.
# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.

TOC_INCLUDE_HEADINGS   = 5

# When enabled doxygen tries to link words that correspond to documented
# classes, or namespaces to their corresponding documentation. Such a link can
# be prevented in individual cases by putting a % sign in front of the word or
# globally by setting AUTOLINK_SUPPORT to NO.
# The default value is: YES.

AUTOLINK_SUPPORT       = YES

# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
# to include (a tag file for) the STL sources as input, then you should set this
# tag to YES in order to let doxygen match functions declarations and
# definitions whose arguments contain STL classes (e.g. func(std::string);
# versus func(std::string) {}). This also make the inheritance and collaboration
# diagrams that involve STL classes more complete and accurate.
# The default value is: NO.

BUILTIN_STL_SUPPORT    = NO

# If you use Microsoft's C++/CLI language, you should set this option to YES to
# enable parsing support.
# The default value is: NO.

CPP_CLI_SUPPORT        = NO

# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
# will parse them like normal C++ but will assume all classes use public instead
# of private inheritance when no explicit protection keyword is present.
# The default value is: NO.

SIP_SUPPORT            = NO

# For Microsoft's IDL there are propget and propput attributes to indicate
# getter and setter methods for a property. Setting this option to YES will make
# doxygen to replace the get and set methods by a property in the documentation.
# This will only work if the methods are indeed getting or setting a simple
# type. If this is not the case, or you want to show the methods anyway, you
# should set this option to NO.
# The default value is: YES.

IDL_PROPERTY_SUPPORT   = YES

# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
# tag is set to YES then doxygen will reuse the documentation of the first
# member in the group (if any) for the other members of the group. By default
# all members of a group must be documented explicitly.
# The default value is: NO.

DISTRIBUTE_GROUP_DOC   = NO

# If one adds a struct or class to a group and this option is enabled, then also
# any nested class or struct is added to the same group. By default this option
# is disabled and one has to add nested compounds explicitly via \ingroup.
# The default value is: NO.

GROUP_NESTED_COMPOUNDS = NO

# Set the SUBGROUPING tag to YES to allow class member groups of the same type
# (for instance a group of public functions) to be put as a subgroup of that
# type (e.g. under the Public Functions section). Set it to NO to prevent
# subgrouping. Alternatively, this can be done per class using the
# \nosubgrouping command.
# The default value is: YES.

SUBGROUPING            = YES

# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
# are shown inside the group in which they are included (e.g. using \ingroup)
# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
# and RTF).
#
# Note that this feature does not work in combination with
# SEPARATE_MEMBER_PAGES.
# The default value is: NO.

INLINE_GROUPED_CLASSES = NO

# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
# with only public data fields or simple typedef fields will be shown inline in
# the documentation of the scope in which they are defined (i.e. file,
# namespace, or group documentation), provided this scope is documented. If set
# to NO, structs, classes, and unions are shown on a separate page (for HTML and
# Man pages) or section (for LaTeX and RTF).
# The default value is: NO.

INLINE_SIMPLE_STRUCTS  = NO

# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
# enum is documented as struct, union, or enum with the name of the typedef. So
# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
# with name TypeT. When disabled the typedef will appear as a member of a file,
# namespace, or class. And the struct will be named TypeS. This can typically be
# useful for C code in case the coding convention dictates that all compound
# types are typedef'ed and only the typedef is referenced, never the tag name.
# The default value is: NO.

TYPEDEF_HIDES_STRUCT   = NO

# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
# cache is used to resolve symbols given their name and scope. Since this can be
# an expensive process and often the same symbol appears multiple times in the
# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
# doxygen will become slower. If the cache is too large, memory is wasted. The
# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
# symbols. At the end of a run doxygen will report the cache usage and suggest
# the optimal cache size from a speed point of view.
# Minimum value: 0, maximum value: 9, default value: 0.

LOOKUP_CACHE_SIZE      = 0

#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------

# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
# documentation are documented, even if no documentation was available. Private
# class members and static file members will be hidden unless the
# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
# Note: This will also disable the warnings about undocumented members that are
# normally produced when WARNINGS is set to YES.
# The default value is: NO.

EXTRACT_ALL            = YES

# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
# be included in the documentation.
# The default value is: NO.

EXTRACT_PRIVATE        = NO

# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
# methods of a class will be included in the documentation.
# The default value is: NO.

EXTRACT_PRIV_VIRTUAL   = NO

# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
# scope will be included in the documentation.
# The default value is: NO.

EXTRACT_PACKAGE        = NO

# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
# included in the documentation.
# The default value is: NO.

EXTRACT_STATIC         = NO

# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
# locally in source files will be included in the documentation. If set to NO,
# only classes defined in header files are included. Does not have any effect
# for Java sources.
# The default value is: YES.

EXTRACT_LOCAL_CLASSES  = NO

# This flag is only useful for Objective-C code. If set to YES, local methods,
# which are defined in the implementation section but not in the interface are
# included in the documentation. If set to NO, only methods in the interface are
# included.
# The default value is: NO.

EXTRACT_LOCAL_METHODS  = NO

# If this flag is set to YES, the members of anonymous namespaces will be
# extracted and appear in the documentation as a namespace called
# 'anonymous_namespace{file}', where file will be replaced with the base name of
# the file that contains the anonymous namespace. By default anonymous namespace
# are hidden.
# The default value is: NO.

EXTRACT_ANON_NSPACES   = NO

# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
# undocumented members inside documented classes or files. If set to NO these
# members will be included in the various overviews, but no documentation
# section is generated. This option has no effect if EXTRACT_ALL is enabled.
# The default value is: NO.

HIDE_UNDOC_MEMBERS     = NO

# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
# undocumented classes that are normally visible in the class hierarchy. If set
# to NO, these classes will be included in the various overviews. This option
# has no effect if EXTRACT_ALL is enabled.
# The default value is: NO.

HIDE_UNDOC_CLASSES     = NO

# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
# declarations. If set to NO, these declarations will be included in the
# documentation.
# The default value is: NO.

HIDE_FRIEND_COMPOUNDS  = NO

# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
# documentation blocks found inside the body of a function. If set to NO, these
# blocks will be appended to the function's detailed documentation block.
# The default value is: NO.

HIDE_IN_BODY_DOCS      = YES

# The INTERNAL_DOCS tag determines if documentation that is typed after a
# \internal command is included. If the tag is set to NO then the documentation
# will be excluded. Set it to YES to include the internal documentation.
# The default value is: NO.

INTERNAL_DOCS          = YES

# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
# names in lower-case letters. If set to YES, upper-case letters are also
# allowed. This is useful if you have classes or files whose names only differ
# in case and if your file system supports case sensitive file names. Windows
# (including Cygwin) ands Mac users are advised to set this option to NO.
# The default value is: system dependent.

CASE_SENSE_NAMES       = YES

# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
# their full class and namespace scopes in the documentation. If set to YES, the
# scope will be hidden.
# The default value is: NO.

HIDE_SCOPE_NAMES       = NO

# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
# append additional text to a page's title, such as Class Reference. If set to
# YES the compound reference will be hidden.
# The default value is: NO.

HIDE_COMPOUND_REFERENCE= NO

# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
# the files that are included by a file in the documentation of that file.
# The default value is: YES.

SHOW_INCLUDE_FILES     = YES

# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
# grouped member an include statement to the documentation, telling the reader
# which file to include in order to use the member.
# The default value is: NO.

SHOW_GROUPED_MEMB_INC  = NO

# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
# files with double quotes in the documentation rather than with sharp brackets.
# The default value is: NO.

FORCE_LOCAL_INCLUDES   = YES

# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
# documentation for inline members.
# The default value is: YES.

INLINE_INFO            = YES

# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
# (detailed) documentation of file and class members alphabetically by member
# name. If set to NO, the members will appear in declaration order.
# The default value is: YES.

SORT_MEMBER_DOCS       = YES

# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
# descriptions of file, namespace and class members alphabetically by member
# name. If set to NO, the members will appear in declaration order. Note that
# this will also influence the order of the classes in the class list.
# The default value is: NO.

SORT_BRIEF_DOCS        = YES

# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
# (brief and detailed) documentation of class members so that constructors and
# destructors are listed first. If set to NO the constructors will appear in the
# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
# member documentation.
# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
# detailed member documentation.
# The default value is: NO.

SORT_MEMBERS_CTORS_1ST = YES

# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
# of group names into alphabetical order. If set to NO the group names will
# appear in their defined order.
# The default value is: NO.

SORT_GROUP_NAMES       = YES

# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
# fully-qualified names, including namespaces. If set to NO, the class list will
# be sorted only by class name, not including the namespace part.
# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
# Note: This option applies only to the class list, not to the alphabetical
# list.
# The default value is: NO.

SORT_BY_SCOPE_NAME     = YES

# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
# type resolution of all parameters of a function it will reject a match between
# the prototype and the implementation of a member function even if there is
# only one candidate or it is obvious which candidate to choose by doing a
# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
# accept a match between prototype and implementation in such cases.
# The default value is: NO.

STRICT_PROTO_MATCHING  = NO

# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
# list. This list is created by putting \todo commands in the documentation.
# The default value is: YES.

GENERATE_TODOLIST      = YES

# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
# list. This list is created by putting \test commands in the documentation.
# The default value is: YES.

GENERATE_TESTLIST      = YES

# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
# list. This list is created by putting \bug commands in the documentation.
# The default value is: YES.

GENERATE_BUGLIST       = YES

# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
# the deprecated list. This list is created by putting \deprecated commands in
# the documentation.
# The default value is: YES.

GENERATE_DEPRECATEDLIST= YES

# The ENABLED_SECTIONS tag can be used to enable conditional documentation
# sections, marked by \if <section_label> ... \endif and \cond <section_label>
# ... \endcond blocks.

ENABLED_SECTIONS       =

# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
# initial value of a variable or macro / define can have for it to appear in the
# documentation. If the initializer consists of more lines than specified here
# it will be hidden. Use a value of 0 to hide initializers completely. The
# appearance of the value of individual variables and macros / defines can be
# controlled using \showinitializer or \hideinitializer command in the
# documentation regardless of this setting.
# Minimum value: 0, maximum value: 10000, default value: 30.

MAX_INITIALIZER_LINES  = 30

# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
# the bottom of the documentation of classes and structs. If set to YES, the
# list will mention the files that were used to generate the documentation.
# The default value is: YES.

SHOW_USED_FILES        = YES

# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
# will remove the Files entry from the Quick Index and from the Folder Tree View
# (if specified).
# The default value is: YES.

SHOW_FILES             = YES

# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
# page. This will remove the Namespaces entry from the Quick Index and from the
# Folder Tree View (if specified).
# The default value is: YES.

SHOW_NAMESPACES        = YES

# The FILE_VERSION_FILTER tag can be used to specify a program or script that
# doxygen should invoke to get the current version for each file (typically from
# the version control system). Doxygen will invoke the program by executing (via
# popen()) the command command input-file, where command is the value of the
# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
# by doxygen. Whatever the program writes to standard output is used as the file
# version. For an example see the documentation.

FILE_VERSION_FILTER    =

# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
# by doxygen. The layout file controls the global structure of the generated
# output files in an output format independent way. To create the layout file
# that represents doxygen's defaults, run doxygen with the -l option. You can
# optionally specify a file name after the option, if omitted DoxygenLayout.xml
# will be used as the name of the layout file.
#
# Note that if you run doxygen from a directory containing a file called
# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
# tag is left empty.

LAYOUT_FILE            =

# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
# the reference definitions. This must be a list of .bib files. The .bib
# extension is automatically appended if omitted. This requires the bibtex tool
# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
# For LaTeX the style of the bibliography can be controlled using
# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
# search path. See also \cite for info how to create references.

CITE_BIB_FILES         =

#---------------------------------------------------------------------------
# Configuration options related to warning and progress messages
#---------------------------------------------------------------------------

# The QUIET tag can be used to turn on/off the messages that are generated to
# standard output by doxygen. If QUIET is set to YES this implies that the
# messages are off.
# The default value is: NO.

QUIET                  = YES

# The WARNINGS tag can be used to turn on/off the warning messages that are
# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
# this implies that the warnings are on.
#
# Tip: Turn warnings on while writing the documentation.
# The default value is: YES.

WARNINGS               = YES

# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
# will automatically be disabled.
# The default value is: YES.

WARN_IF_UNDOCUMENTED   = YES

# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
# potential errors in the documentation, such as not documenting some parameters
# in a documented function, or documenting parameters that don't exist or using
# markup commands wrongly.
# The default value is: YES.

WARN_IF_DOC_ERROR      = YES

# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
# are documented, but have no documentation for their parameters or return
# value. If set to NO, doxygen will only warn about wrong or incomplete
# parameter documentation, but not about the absence of documentation. If
# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
# The default value is: NO.

WARN_NO_PARAMDOC       = NO

# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
# a warning is encountered.
# The default value is: NO.

WARN_AS_ERROR          = NO

# The WARN_FORMAT tag determines the format of the warning messages that doxygen
# can produce. The string should contain the $file, $line, and $text tags, which
# will be replaced by the file and line number from which the warning originated
# and the warning text. Optionally the format may contain $version, which will
# be replaced by the version of the file (if it could be obtained via
# FILE_VERSION_FILTER)
# The default value is: $file:$line: $text.

WARN_FORMAT            = "$file:$line: $text "

# The WARN_LOGFILE tag can be used to specify a file to which warning and error
# messages should be written. If left blank the output is written to standard
# error (stderr).

WARN_LOGFILE           =

#---------------------------------------------------------------------------
# Configuration options related to the input files
#---------------------------------------------------------------------------

# The INPUT tag is used to specify the files and/or directories that contain
# documented source files. You may enter file names like myfile.cpp or
# directories like /usr/src/myproject. Separate the files or directories with
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.

INPUT                  = ../include

# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
# documentation (see: https://www.gnu.org/software/libiconv/) for the list of
# possible encodings.
# The default value is: UTF-8.

INPUT_ENCODING         = UTF-8

# If the value of the INPUT tag contains directories, you can use the
# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
# *.h) to filter out the source-files in the directories.
#
# Note that for custom extensions or not directly supported extensions you also
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
# read by doxygen.
#
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen
# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f, *.for, *.tcl, *.vhd,
# *.vhdl, *.ucf, *.qsf and *.ice.

FILE_PATTERNS          = *.h \
                         *.hpp \
                         fsl_schema_*_cstr.c

# The RECURSIVE tag can be used to specify whether or not subdirectories should
# be searched for input files as well.
# The default value is: NO.

RECURSIVE              = YES

# The EXCLUDE tag can be used to specify files and/or directories that should be
# excluded from the INPUT source files. This way you can easily exclude a
# subdirectory from a directory tree whose root is specified with the INPUT tag.
#
# Note that relative paths are relative to the directory from which doxygen is
# run.

EXCLUDE                =

# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
# directories that are symbolic links (a Unix file system feature) are excluded
# from the input.
# The default value is: NO.

EXCLUDE_SYMLINKS       = YES

# If the value of the INPUT tag contains directories, you can use the
# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
# certain files from those directories.
#
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories for example use the pattern */test/*

EXCLUDE_PATTERNS       = *test*

# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
# (namespaces, classes, functions, etc.) that should be excluded from the
# output. The symbol name can be a fully qualified name, a word, or if the
# wildcard * is used, a substring. Examples: ANamespace, AClass,
# AClass::ANamespace, ANamespace::*Test
#
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories use the pattern */test/*

EXCLUDE_SYMBOLS        = *_INCLUDED*

# The EXAMPLE_PATH tag can be used to specify one or more files or directories
# that contain example code fragments that are included (see the \include
# command).

EXAMPLE_PATH           =

# If the value of the EXAMPLE_PATH tag contains directories, you can use the
# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
# *.h) to filter out the source-files in the directories. If left blank all
# files are included.

EXAMPLE_PATTERNS       =

# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
# searched for input files to be used with the \include or \dontinclude commands
# irrespective of the value of the RECURSIVE tag.
# The default value is: NO.

EXAMPLE_RECURSIVE      = NO

# The IMAGE_PATH tag can be used to specify one or more files or directories
# that contain images that are to be included in the documentation (see the
# \image command).

IMAGE_PATH             =

# The INPUT_FILTER tag can be used to specify a program that doxygen should
# invoke to filter for each input file. Doxygen will invoke the filter program
# by executing (via popen()) the command:
#
# <filter> <input-file>
#
# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
# name of an input file. Doxygen will then use the output that the filter
# program writes to standard output. If FILTER_PATTERNS is specified, this tag
# will be ignored.
#
# Note that the filter must not add or remove lines; it is applied before the
# code is scanned, but not when the output code is generated. If lines are added
# or removed, the anchors will not be placed correctly.
#
# Note that for custom extensions or not directly supported extensions you also
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
# properly processed by doxygen.

INPUT_FILTER           = "sed -e 's/^\s*\*\*//'"

# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
# basis. Doxygen will compare the file name with each pattern and apply the
# filter if there is a match. The filters are a list of the form: pattern=filter
# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
# patterns match the file name, INPUT_FILTER is applied.
#
# Note that for custom extensions or not directly supported extensions you also
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
# properly processed by doxygen.

FILTER_PATTERNS        =

# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
# INPUT_FILTER) will also be used to filter the input files that are used for
# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
# The default value is: NO.

FILTER_SOURCE_FILES    = NO

# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
# it is also possible to disable source filtering for a specific pattern using
# *.ext= (so without naming a filter).
# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.

FILTER_SOURCE_PATTERNS =

# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
# is part of the input, its contents will be placed on the main page
# (index.html). This can be useful if you have a project on for instance GitHub
# and want to reuse the introduction page also for the doxygen output.

USE_MDFILE_AS_MAINPAGE =

#---------------------------------------------------------------------------
# Configuration options related to source browsing
#---------------------------------------------------------------------------

# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
# generated. Documented entities will be cross-referenced with these sources.
#
# Note: To get rid of all source code in the generated output, make sure that
# also VERBATIM_HEADERS is set to NO.
# The default value is: NO.

SOURCE_BROWSER         = YES

# Setting the INLINE_SOURCES tag to YES will include the body of functions,
# classes and enums directly into the documentation.
# The default value is: NO.

INLINE_SOURCES         = NO

# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
# special comment blocks from generated source code fragments. Normal C, C++ and
# Fortran comments will always remain visible.
# The default value is: YES.

STRIP_CODE_COMMENTS    = NO

# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
# entity all documented functions referencing it will be listed.
# The default value is: NO.

REFERENCED_BY_RELATION = YES

# If the REFERENCES_RELATION tag is set to YES then for each documented function
# all documented entities called/used by that function will be listed.
# The default value is: NO.

REFERENCES_RELATION    = YES

# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
# to YES then the hyperlinks from functions in REFERENCES_RELATION and
# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
# link to the documentation.
# The default value is: YES.

REFERENCES_LINK_SOURCE = YES

# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
# source code will show a tooltip with additional information such as prototype,
# brief description and links to the definition and documentation. Since this
# will make the HTML file larger and loading of large files a bit slower, you
# can opt to disable this feature.
# The default value is: YES.
# This tag requires that the tag SOURCE_BROWSER is set to YES.

SOURCE_TOOLTIPS        = YES

# If the USE_HTAGS tag is set to YES then the references to source code will
# point to the HTML generated by the htags(1) tool instead of doxygen built-in
# source browser. The htags tool is part of GNU's global source tagging system
# (see https://www.gnu.org/software/global/global.html). You will need version
# 4.8.6 or higher.
#
# To use it do the following:
# - Install the latest version of global
# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
# - Make sure the INPUT points to the root of the source tree
# - Run doxygen as normal
#
# Doxygen will invoke htags (and that will in turn invoke gtags), so these
# tools must be available from the command line (i.e. in the search path).
#
# The result: instead of the source browser generated by doxygen, the links to
# source code will now point to the output of htags.
# The default value is: NO.
# This tag requires that the tag SOURCE_BROWSER is set to YES.

USE_HTAGS              = NO

# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
# verbatim copy of the header file for each class for which an include is
# specified. Set to NO to disable this.
# See also: Section \class.
# The default value is: YES.

VERBATIM_HEADERS       = YES

# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the
# cost of reduced performance. This can be particularly helpful with template
# rich C++ code for which doxygen's built-in parser lacks the necessary type
# information.
# Note: The availability of this option depends on whether or not doxygen was
# generated with the -Duse_libclang=ON option for CMake.
# The default value is: NO.

CLANG_ASSISTED_PARSING = NO

# If clang assisted parsing is enabled you can provide the compiler with command
# line options that you would normally use when invoking the compiler. Note that
# the include paths will already be set by doxygen for the files and directories
# specified with INPUT and INCLUDE_PATH.
# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.

CLANG_OPTIONS          =

# If clang assisted parsing is enabled you can provide the clang parser with the
# path to the compilation database (see:
# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) used when the files
# were built. This is equivalent to specifying the "-p" option to a clang tool,
# such as clang-check. These options will then be passed to the parser.
# Note: The availability of this option depends on whether or not doxygen was
# generated with the -Duse_libclang=ON option for CMake.

CLANG_DATABASE_PATH    =

#---------------------------------------------------------------------------
# Configuration options related to the alphabetical class index
#---------------------------------------------------------------------------

# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
# compounds will be generated. Enable this if the project contains a lot of
# classes, structs, unions or interfaces.
# The default value is: YES.

ALPHABETICAL_INDEX     = YES

# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
# which the alphabetical index list will be split.
# Minimum value: 1, maximum value: 20, default value: 5.
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.

COLS_IN_ALPHA_INDEX    = 5

# In case all classes in a project start with a common prefix, all classes will
# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
# can be used to specify a prefix (or a list of prefixes) that should be ignored
# while generating the index headers.
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.

IGNORE_PREFIX          =

#---------------------------------------------------------------------------
# Configuration options related to the HTML output
#---------------------------------------------------------------------------

# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
# The default value is: YES.

GENERATE_HTML          = YES

# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
# it.
# The default directory is: html.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_OUTPUT            = libfossil-API-html

# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
# generated HTML page (for example: .htm, .php, .asp).
# The default value is: .html.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_FILE_EXTENSION    = .html

# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
# each generated HTML page. If the tag is left blank doxygen will generate a
# standard header.
#
# To get valid HTML the header file that includes any scripts and style sheets
# that doxygen needs, which is dependent on the configuration options used (e.g.
# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
# default header using
# doxygen -w html new_header.html new_footer.html new_stylesheet.css
# YourConfigFile
# and then modify the file new_header.html. See also section "Doxygen usage"
# for information on how to generate the default header that doxygen normally
# uses.
# Note: The header is subject to change so you typically have to regenerate the
# default header when upgrading to a newer version of doxygen. For a description
# of the possible markers and block names see the documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_HEADER            =

# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
# generated HTML page. If the tag is left blank doxygen will generate a standard
# footer. See HTML_HEADER for more information on how to generate a default
# footer and what special commands can be used inside the footer. See also
# section "Doxygen usage" for information on how to generate the default footer
# that doxygen normally uses.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_FOOTER            =

# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
# sheet that is used by each HTML page. It can be used to fine-tune the look of
# the HTML output. If left blank doxygen will generate a default style sheet.
# See also section "Doxygen usage" for information on how to generate the style
# sheet that doxygen normally uses.
# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
# it is more robust and this tag (HTML_STYLESHEET) will in the future become
# obsolete.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_STYLESHEET        =

# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
# cascading style sheets that are included after the standard style sheets
# created by doxygen. Using this option one can overrule certain style aspects.
# This is preferred over using HTML_STYLESHEET since it does not replace the
# standard style sheet and is therefore more robust against future updates.
# Doxygen will copy the style sheet files to the output directory.
# Note: The order of the extra style sheet files is of importance (e.g. the last
# style sheet in the list overrules the setting of the previous ones in the
# list). For an example see the documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_EXTRA_STYLESHEET  =

# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
# other source files which should be copied to the HTML output directory. Note
# that these files will be copied to the base HTML output directory. Use the
# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
# files will be copied as-is; there are no commands or markers available.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_EXTRA_FILES       =

# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
# will adjust the colors in the style sheet and background images according to
# this color. Hue is specified as an angle on a colorwheel, see
# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
# purple, and 360 is red again.
# Minimum value: 0, maximum value: 359, default value: 220.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_COLORSTYLE_HUE    = 220

# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
# in the HTML output. For a value of 0 the output will use grayscales only. A
# value of 255 will produce the most vivid colors.
# Minimum value: 0, maximum value: 255, default value: 100.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_COLORSTYLE_SAT    = 100

# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
# luminance component of the colors in the HTML output. Values below 100
# gradually make the output lighter, whereas values above 100 make the output
# darker. The value divided by 100 is the actual gamma applied, so 80 represents
# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
# change the gamma.
# Minimum value: 40, maximum value: 240, default value: 80.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_COLORSTYLE_GAMMA  = 80

# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
# page will contain the date and time when the page was generated. Setting this
# to YES can help to show when doxygen was last run and thus if the
# documentation is up to date.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_TIMESTAMP         = YES

# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
# documentation will contain a main index with vertical navigation menus that
# are dynamically created via JavaScript. If disabled, the navigation index will
# consists of multiple levels of tabs that are statically embedded in every HTML
# page. Disable this option to support browsers that do not have JavaScript,
# like the Qt help browser.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_DYNAMIC_MENUS     = YES

# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
# documentation will contain sections that can be hidden and shown after the
# page has loaded.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_DYNAMIC_SECTIONS  = NO

# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
# shown in the various tree structured indices initially; the user can expand
# and collapse entries dynamically later on. Doxygen will expand the tree to
# such a level that at most the specified number of entries are visible (unless
# a fully collapsed tree already exceeds this amount). So setting the number of
# entries 1 will produce a full collapsed tree by default. 0 is a special value
# representing an infinite number of entries and will result in a full expanded
# tree by default.
# Minimum value: 0, maximum value: 9999, default value: 100.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_INDEX_NUM_ENTRIES = 100

# If the GENERATE_DOCSET tag is set to YES, additional index files will be
# generated that can be used as input for Apple's Xcode 3 integrated development
# environment (see: https://developer.apple.com/xcode/), introduced with OSX
# 10.5 (Leopard). To create a documentation set, doxygen will generate a
# Makefile in the HTML output directory. Running make will produce the docset in
# that directory and running make install will install the docset in
# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
# genXcode/_index.html for more information.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.

GENERATE_DOCSET        = NO

# This tag determines the name of the docset feed. A documentation feed provides
# an umbrella under which multiple documentation sets from a single provider
# (such as a company or product suite) can be grouped.
# The default value is: Doxygen generated docs.
# This tag requires that the tag GENERATE_DOCSET is set to YES.

DOCSET_FEEDNAME        = "Doxygen generated docs"

# This tag specifies a string that should uniquely identify the documentation
# set bundle. This should be a reverse domain-name style string, e.g.
# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
# The default value is: org.doxygen.Project.
# This tag requires that the tag GENERATE_DOCSET is set to YES.

DOCSET_BUNDLE_ID       = org.doxygen.Project

# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
# the documentation publisher. This should be a reverse domain-name style
# string, e.g. com.mycompany.MyDocSet.documentation.
# The default value is: org.doxygen.Publisher.
# This tag requires that the tag GENERATE_DOCSET is set to YES.

DOCSET_PUBLISHER_ID    = org.doxygen.Publisher

# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
# The default value is: Publisher.
# This tag requires that the tag GENERATE_DOCSET is set to YES.

DOCSET_PUBLISHER_NAME  = Publisher

# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on
# Windows.
#
# The HTML Help Workshop contains a compiler that can convert all HTML output
# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
# files are now used as the Windows 98 help format, and will replace the old
# Windows help format (.hlp) on all Windows platforms in the future. Compressed
# HTML files also contain an index, a table of contents, and you can search for
# words in the documentation. The HTML workshop also contains a viewer for
# compressed HTML files.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.

GENERATE_HTMLHELP      = NO

# The CHM_FILE tag can be used to specify the file name of the resulting .chm
# file. You can add a path in front of the file if the result should not be
# written to the html output directory.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.

CHM_FILE               =

# The HHC_LOCATION tag can be used to specify the location (absolute path
# including file name) of the HTML help compiler (hhc.exe). If non-empty,
# doxygen will try to run the HTML help compiler on the generated index.hhp.
# The file has to be specified with full path.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.

HHC_LOCATION           =

# The GENERATE_CHI flag controls if a separate .chi index file is generated
# (YES) or that it should be included in the master .chm file (NO).
# The default value is: NO.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.

GENERATE_CHI           = NO

# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
# and project file content.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.

CHM_INDEX_ENCODING     =

# The BINARY_TOC flag controls whether a binary table of contents is generated
# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
# enables the Previous and Next buttons.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.

BINARY_TOC             = NO

# The TOC_EXPAND flag can be set to YES to add extra items for group members to
# the table of contents of the HTML help documentation and to the tree view.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.

TOC_EXPAND             = NO

# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
# (.qch) of the generated HTML documentation.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.

GENERATE_QHP           = NO

# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
# the file name of the resulting .qch file. The path specified is relative to
# the HTML output folder.
# This tag requires that the tag GENERATE_QHP is set to YES.

QCH_FILE               =

# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
# Project output. For more information please see Qt Help Project / Namespace
# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
# The default value is: org.doxygen.Project.
# This tag requires that the tag GENERATE_QHP is set to YES.

QHP_NAMESPACE          =

# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
# Help Project output. For more information please see Qt Help Project / Virtual
# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-
# folders).
# The default value is: doc.
# This tag requires that the tag GENERATE_QHP is set to YES.

QHP_VIRTUAL_FOLDER     = doc

# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
# filter to add. For more information please see Qt Help Project / Custom
# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
# filters).
# This tag requires that the tag GENERATE_QHP is set to YES.

QHP_CUST_FILTER_NAME   =

# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
# custom filter to add. For more information please see Qt Help Project / Custom
# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
# filters).
# This tag requires that the tag GENERATE_QHP is set to YES.

QHP_CUST_FILTER_ATTRS  =

# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
# project's filter section matches. Qt Help Project / Filter Attributes (see:
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
# This tag requires that the tag GENERATE_QHP is set to YES.

QHP_SECT_FILTER_ATTRS  =

# The QHG_LOCATION tag can be used to specify the location of Qt's
# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
# generated .qhp file.
# This tag requires that the tag GENERATE_QHP is set to YES.

QHG_LOCATION           =

# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
# generated, together with the HTML files, they form an Eclipse help plugin. To
# install this plugin and make it available under the help contents menu in
# Eclipse, the contents of the directory containing the HTML and XML files needs
# to be copied into the plugins directory of eclipse. The name of the directory
# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
# After copying Eclipse needs to be restarted before the help appears.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.

GENERATE_ECLIPSEHELP   = NO

# A unique identifier for the Eclipse help plugin. When installing the plugin
# the directory name containing the HTML and XML files should also have this
# name. Each documentation set should have its own identifier.
# The default value is: org.doxygen.Project.
# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.

ECLIPSE_DOC_ID         = org.doxygen.Project

# If you want full control over the layout of the generated HTML pages it might
# be necessary to disable the index and replace it with your own. The
# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
# of each HTML page. A value of NO enables the index and the value YES disables
# it. Since the tabs in the index contain the same information as the navigation
# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.

DISABLE_INDEX          = NO

# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
# structure should be generated to display hierarchical information. If the tag
# value is set to YES, a side panel will be generated containing a tree-like
# index structure (just like the one that is generated for HTML Help). For this
# to work a browser that supports JavaScript, DHTML, CSS and frames is required
# (i.e. any modern browser). Windows users are probably better off using the
# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
# further fine-tune the look of the index. As an example, the default style
# sheet generated by doxygen has an example that shows how to put an image at
# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
# the same information as the tab index, you could consider setting
# DISABLE_INDEX to YES when enabling this option.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.

GENERATE_TREEVIEW      = YES

# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
# doxygen will group on one line in the generated HTML documentation.
#
# Note that a value of 0 will completely suppress the enum values from appearing
# in the overview section.
# Minimum value: 0, maximum value: 20, default value: 4.
# This tag requires that the tag GENERATE_HTML is set to YES.

ENUM_VALUES_PER_LINE   = 1

# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
# to set the initial width (in pixels) of the frame in which the tree is shown.
# Minimum value: 0, maximum value: 1500, default value: 250.
# This tag requires that the tag GENERATE_HTML is set to YES.

TREEVIEW_WIDTH         = 300

# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
# external symbols imported via tag files in a separate window.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.

EXT_LINKS_IN_WINDOW    = NO

# Use this tag to change the font size of LaTeX formulas included as images in
# the HTML documentation. When you change the font size after a successful
# doxygen run you need to manually remove any form_*.png images from the HTML
# output directory to force them to be regenerated.
# Minimum value: 8, maximum value: 50, default value: 10.
# This tag requires that the tag GENERATE_HTML is set to YES.

FORMULA_FONTSIZE       = 10

# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
# generated for formulas are transparent PNGs. Transparent PNGs are not
# supported properly for IE 6.0, but are supported on all modern browsers.
#
# Note that when changing this option you need to delete any form_*.png files in
# the HTML output directory before the changes have effect.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.

FORMULA_TRANSPARENT    = YES

# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
# to create new LaTeX commands to be used in formulas as building blocks. See
# the section "Including formulas" for details.

FORMULA_MACROFILE      =

# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
# https://www.mathjax.org) which uses client side JavaScript for the rendering
# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
# installed or if you want to formulas look prettier in the HTML output. When
# enabled you may also need to install MathJax separately and configure the path
# to it using the MATHJAX_RELPATH option.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.

USE_MATHJAX            = NO

# When MathJax is enabled you can set the default output format to be used for
# the MathJax output. See the MathJax site (see:
# http://docs.mathjax.org/en/latest/output.html) for more details.
# Possible values are: HTML-CSS (which is slower, but has the best
# compatibility), NativeMML (i.e. MathML) and SVG.
# The default value is: HTML-CSS.
# This tag requires that the tag USE_MATHJAX is set to YES.

MATHJAX_FORMAT         = HTML-CSS

# When MathJax is enabled you need to specify the location relative to the HTML
# output directory using the MATHJAX_RELPATH option. The destination directory
# should contain the MathJax.js script. For instance, if the mathjax directory
# is located at the same level as the HTML output directory, then
# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
# Content Delivery Network so you can quickly see the result without installing
# MathJax. However, it is strongly recommended to install a local copy of
# MathJax from https://www.mathjax.org before deployment.
# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/.
# This tag requires that the tag USE_MATHJAX is set to YES.

MATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest

# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
# extension names that should be enabled during MathJax rendering. For example
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
# This tag requires that the tag USE_MATHJAX is set to YES.

MATHJAX_EXTENSIONS     =

# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
# of code that will be used on startup of the MathJax code. See the MathJax site
# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
# example see the documentation.
# This tag requires that the tag USE_MATHJAX is set to YES.

MATHJAX_CODEFILE       =

# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
# the HTML output. The underlying search engine uses javascript and DHTML and
# should work on any modern browser. Note that when using HTML help
# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
# there is already a search function so this one should typically be disabled.
# For large projects the javascript based search engine can be slow, then
# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
# search using the keyboard; to jump to the search box use <access key> + S
# (what the <access key> is depends on the OS and browser, but it is typically
# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
# key> to jump into the search results window, the results can be navigated
# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
# the search. The filter options can be selected when the cursor is inside the
# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
# to select a filter and <Enter> or <escape> to activate or cancel the filter
# option.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.

SEARCHENGINE           = NO

# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
# implemented using a web server instead of a web client using JavaScript. There
# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
# setting. When disabled, doxygen will generate a PHP script for searching and
# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
# and searching needs to be provided by external tools. See the section
# "External Indexing and Searching" for details.
# The default value is: NO.
# This tag requires that the tag SEARCHENGINE is set to YES.

SERVER_BASED_SEARCH    = NO

# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
# script for searching. Instead the search results are written to an XML file
# which needs to be processed by an external indexer. Doxygen will invoke an
# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
# search results.
#
# Doxygen ships with an example indexer (doxyindexer) and search engine
# (doxysearch.cgi) which are based on the open source search engine library
# Xapian (see: https://xapian.org/).
#
# See the section "External Indexing and Searching" for details.
# The default value is: NO.
# This tag requires that the tag SEARCHENGINE is set to YES.

EXTERNAL_SEARCH        = NO

# The SEARCHENGINE_URL should point to a search engine hosted by a web server
# which will return the search results when EXTERNAL_SEARCH is enabled.
#
# Doxygen ships with an example indexer (doxyindexer) and search engine
# (doxysearch.cgi) which are based on the open source search engine library
# Xapian (see: https://xapian.org/). See the section "External Indexing and
# Searching" for details.
# This tag requires that the tag SEARCHENGINE is set to YES.

SEARCHENGINE_URL       =

# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
# search data is written to a file for indexing by an external tool. With the
# SEARCHDATA_FILE tag the name of this file can be specified.
# The default file is: searchdata.xml.
# This tag requires that the tag SEARCHENGINE is set to YES.

SEARCHDATA_FILE        = searchdata.xml

# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
# projects and redirect the results back to the right project.
# This tag requires that the tag SEARCHENGINE is set to YES.

EXTERNAL_SEARCH_ID     =

# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
# projects other than the one defined by this configuration file, but that are
# all added to the same external search index. Each project needs to have a
# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
# to a relative location where the documentation can be found. The format is:
# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
# This tag requires that the tag SEARCHENGINE is set to YES.

EXTRA_SEARCH_MAPPINGS  =

#---------------------------------------------------------------------------
# Configuration options related to the LaTeX output
#---------------------------------------------------------------------------

# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
# The default value is: YES.

GENERATE_LATEX         = NO

# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
# it.
# The default directory is: latex.
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_OUTPUT           = libcson-API-latex

# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
# invoked.
#
# Note that when not enabling USE_PDFLATEX the default is latex when enabling
# USE_PDFLATEX the default is pdflatex and when in the later case latex is
# chosen this is overwritten by pdflatex. For specific output languages the
# default can have been set differently, this depends on the implementation of
# the output language.
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_CMD_NAME         = latex

# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
# index for LaTeX.
# Note: This tag is used in the Makefile / make.bat.
# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
# (.tex).
# The default file is: makeindex.
# This tag requires that the tag GENERATE_LATEX is set to YES.

MAKEINDEX_CMD_NAME     = makeindex

# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
# generate index for LaTeX. In case there is no backslash (\) as first character
# it will be automatically added in the LaTeX code.
# Note: This tag is used in the generated output file (.tex).
# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
# The default value is: makeindex.
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_MAKEINDEX_CMD    = makeindex

# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
# documents. This may be useful for small projects and may help to save some
# trees in general.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.

COMPACT_LATEX          = NO

# The PAPER_TYPE tag can be used to set the paper type that is used by the
# printer.
# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
# 14 inches) and executive (7.25 x 10.5 inches).
# The default value is: a4.
# This tag requires that the tag GENERATE_LATEX is set to YES.

PAPER_TYPE             = a4wide

# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
# that should be included in the LaTeX output. The package can be specified just
# by its name or with the correct syntax as to be used with the LaTeX
# \usepackage command. To get the times font for instance you can specify :
# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
# To use the option intlimits with the amsmath package you can specify:
# EXTRA_PACKAGES=[intlimits]{amsmath}
# If left blank no extra packages will be included.
# This tag requires that the tag GENERATE_LATEX is set to YES.

EXTRA_PACKAGES         =

# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
# generated LaTeX document. The header should contain everything until the first
# chapter. If it is left blank doxygen will generate a standard header. See
# section "Doxygen usage" for information on how to let doxygen write the
# default header to a separate file.
#
# Note: Only use a user-defined header if you know what you are doing! The
# following commands have a special meaning inside the header: $title,
# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
# string, for the replacement values of the other commands the user is referred
# to HTML_HEADER.
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_HEADER           =

# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
# generated LaTeX document. The footer should contain everything after the last
# chapter. If it is left blank doxygen will generate a standard footer. See
# LATEX_HEADER for more information on how to generate a default footer and what
# special commands can be used inside the footer.
#
# Note: Only use a user-defined footer if you know what you are doing!
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_FOOTER           =

# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
# LaTeX style sheets that are included after the standard style sheets created
# by doxygen. Using this option one can overrule certain style aspects. Doxygen
# will copy the style sheet files to the output directory.
# Note: The order of the extra style sheet files is of importance (e.g. the last
# style sheet in the list overrules the setting of the previous ones in the
# list).
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_EXTRA_STYLESHEET =

# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
# other source files which should be copied to the LATEX_OUTPUT output
# directory. Note that the files will be copied as-is; there are no commands or
# markers available.
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_EXTRA_FILES      =

# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
# contain links (just like the HTML output) instead of page references. This
# makes the output suitable for online browsing using a PDF viewer.
# The default value is: YES.
# This tag requires that the tag GENERATE_LATEX is set to YES.

PDF_HYPERLINKS         = YES

# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
# the PDF file directly from the LaTeX files. Set this option to YES, to get a
# higher quality PDF documentation.
# The default value is: YES.
# This tag requires that the tag GENERATE_LATEX is set to YES.

USE_PDFLATEX           = NO

# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
# command to the generated LaTeX files. This will instruct LaTeX to keep running
# if errors occur, instead of asking the user for help. This option is also used
# when generating formulas in HTML.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_BATCHMODE        = YES

# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
# index chapters (such as File Index, Compound Index, etc.) in the output.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_HIDE_INDICES     = NO

# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
# code with syntax highlighting in the LaTeX output.
#
# Note that which sources are shown also depends on other settings such as
# SOURCE_BROWSER.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_SOURCE_CODE      = NO

# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
# bibliography, e.g. plainnat, or ieeetr. See
# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
# The default value is: plain.
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_BIB_STYLE        = plain

# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
# page will contain the date and time when the page was generated. Setting this
# to NO can help when comparing the output of multiple runs.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_TIMESTAMP        = NO

# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
# path from which the emoji images will be read. If a relative path is entered,
# it will be relative to the LATEX_OUTPUT directory. If left blank the
# LATEX_OUTPUT directory will be used.
# This tag requires that the tag GENERATE_LATEX is set to YES.

LATEX_EMOJI_DIRECTORY  =

#---------------------------------------------------------------------------
# Configuration options related to the RTF output
#---------------------------------------------------------------------------

# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
# RTF output is optimized for Word 97 and may not look too pretty with other RTF
# readers/editors.
# The default value is: NO.

GENERATE_RTF           = NO

# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
# it.
# The default directory is: rtf.
# This tag requires that the tag GENERATE_RTF is set to YES.

RTF_OUTPUT             = rtf

# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
# documents. This may be useful for small projects and may help to save some
# trees in general.
# The default value is: NO.
# This tag requires that the tag GENERATE_RTF is set to YES.

COMPACT_RTF            = NO

# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
# contain hyperlink fields. The RTF file will contain links (just like the HTML
# output) instead of page references. This makes the output suitable for online
# browsing using Word or some other Word compatible readers that support those
# fields.
#
# Note: WordPad (write) and others do not support links.
# The default value is: NO.
# This tag requires that the tag GENERATE_RTF is set to YES.

RTF_HYPERLINKS         = NO

# Load stylesheet definitions from file. Syntax is similar to doxygen's
# configuration file, i.e. a series of assignments. You only have to provide
# replacements, missing definitions are set to their default value.
#
# See also section "Doxygen usage" for information on how to generate the
# default style sheet that doxygen normally uses.
# This tag requires that the tag GENERATE_RTF is set to YES.

RTF_STYLESHEET_FILE    =

# Set optional variables used in the generation of an RTF document. Syntax is
# similar to doxygen's configuration file. A template extensions file can be
# generated using doxygen -e rtf extensionFile.
# This tag requires that the tag GENERATE_RTF is set to YES.

RTF_EXTENSIONS_FILE    =

# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
# with syntax highlighting in the RTF output.
#
# Note that which sources are shown also depends on other settings such as
# SOURCE_BROWSER.
# The default value is: NO.
# This tag requires that the tag GENERATE_RTF is set to YES.

RTF_SOURCE_CODE        = NO

#---------------------------------------------------------------------------
# Configuration options related to the man page output
#---------------------------------------------------------------------------

# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
# classes and files.
# The default value is: NO.

GENERATE_MAN           = NO

# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
# it. A directory man3 will be created inside the directory specified by
# MAN_OUTPUT.
# The default directory is: man.
# This tag requires that the tag GENERATE_MAN is set to YES.

MAN_OUTPUT             = man

# The MAN_EXTENSION tag determines the extension that is added to the generated
# man pages. In case the manual section does not start with a number, the number
# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
# optional.
# The default value is: .3.
# This tag requires that the tag GENERATE_MAN is set to YES.

MAN_EXTENSION          = .3

# The MAN_SUBDIR tag determines the name of the directory created within
# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
# MAN_EXTENSION with the initial . removed.
# This tag requires that the tag GENERATE_MAN is set to YES.

MAN_SUBDIR             =

# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
# will generate one additional man file for each entity documented in the real
# man page(s). These additional files only source the real man page, but without
# them the man command would be unable to find the correct page.
# The default value is: NO.
# This tag requires that the tag GENERATE_MAN is set to YES.

MAN_LINKS              = NO

#---------------------------------------------------------------------------
# Configuration options related to the XML output
#---------------------------------------------------------------------------

# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
# captures the structure of the code including all documentation.
# The default value is: NO.

GENERATE_XML           = NO

# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
# it.
# The default directory is: xml.
# This tag requires that the tag GENERATE_XML is set to YES.

XML_OUTPUT             = xml

# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
# listings (including syntax highlighting and cross-referencing information) to
# the XML output. Note that enabling this will significantly increase the size
# of the XML output.
# The default value is: YES.
# This tag requires that the tag GENERATE_XML is set to YES.

XML_PROGRAMLISTING     = YES

# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
# namespace members in file scope as well, matching the HTML output.
# The default value is: NO.
# This tag requires that the tag GENERATE_XML is set to YES.

XML_NS_MEMB_FILE_SCOPE = NO

#---------------------------------------------------------------------------
# Configuration options related to the DOCBOOK output
#---------------------------------------------------------------------------

# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
# that can be used to generate PDF.
# The default value is: NO.

GENERATE_DOCBOOK       = NO

# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
# front of it.
# The default directory is: docbook.
# This tag requires that the tag GENERATE_DOCBOOK is set to YES.

DOCBOOK_OUTPUT         = docbook

# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
# program listings (including syntax highlighting and cross-referencing
# information) to the DOCBOOK output. Note that enabling this will significantly
# increase the size of the DOCBOOK output.
# The default value is: NO.
# This tag requires that the tag GENERATE_DOCBOOK is set to YES.

DOCBOOK_PROGRAMLISTING = NO

#---------------------------------------------------------------------------
# Configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------

# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
# the structure of the code including all documentation. Note that this feature
# is still experimental and incomplete at the moment.
# The default value is: NO.

GENERATE_AUTOGEN_DEF   = NO

#---------------------------------------------------------------------------
# Configuration options related to the Perl module output
#---------------------------------------------------------------------------

# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
# file that captures the structure of the code including all documentation.
#
# Note that this feature is still experimental and incomplete at the moment.
# The default value is: NO.

GENERATE_PERLMOD       = NO

# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
# output from the Perl module output.
# The default value is: NO.
# This tag requires that the tag GENERATE_PERLMOD is set to YES.

PERLMOD_LATEX          = NO

# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
# formatted so it can be parsed by a human reader. This is useful if you want to
# understand what is going on. On the other hand, if this tag is set to NO, the
# size of the Perl module output will be much smaller and Perl will parse it
# just the same.
# The default value is: YES.
# This tag requires that the tag GENERATE_PERLMOD is set to YES.

PERLMOD_PRETTY         = YES

# The names of the make variables in the generated doxyrules.make file are
# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
# so different doxyrules.make files included by the same Makefile don't
# overwrite each other's variables.
# This tag requires that the tag GENERATE_PERLMOD is set to YES.

PERLMOD_MAKEVAR_PREFIX =

#---------------------------------------------------------------------------
# Configuration options related to the preprocessor
#---------------------------------------------------------------------------

# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
# C-preprocessor directives found in the sources and include files.
# The default value is: YES.

ENABLE_PREPROCESSING   = YES

# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
# in the source code. If set to NO, only conditional compilation will be
# performed. Macro expansion can be done in a controlled way by setting
# EXPAND_ONLY_PREDEF to YES.
# The default value is: NO.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.

MACRO_EXPANSION        = NO

# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
# the macro expansion is limited to the macros specified with the PREDEFINED and
# EXPAND_AS_DEFINED tags.
# The default value is: NO.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.

EXPAND_ONLY_PREDEF     = YES

# If the SEARCH_INCLUDES tag is set to YES, the include files in the
# INCLUDE_PATH will be searched if a #include is found.
# The default value is: YES.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.

SEARCH_INCLUDES        = YES

# The INCLUDE_PATH tag can be used to specify one or more directories that
# contain include files that are not input files but should be processed by the
# preprocessor.
# This tag requires that the tag SEARCH_INCLUDES is set to YES.

INCLUDE_PATH           =

# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
# patterns (like *.h and *.hpp) to filter out the header-files in the
# directories. If left blank, the patterns specified with FILE_PATTERNS will be
# used.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.

INCLUDE_FILE_PATTERNS  =

# The PREDEFINED tag can be used to specify one or more macro names that are
# defined before the preprocessor is started (similar to the -D option of e.g.
# gcc). The argument of the tag is a list of macros of the form: name or
# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
# is assumed. To prevent a macro definition from being undefined via #undef or
# recursively expanded use the := operator instead of the = operator.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.

PREDEFINED             = __cplusplus

# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
# tag can be used to specify a list of macro names that should be expanded. The
# macro definition that is found in the sources will be used. Use the PREDEFINED
# tag if you want to use a different macro definition that overrules the
# definition found in the source code.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.

EXPAND_AS_DEFINED      =

# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
# remove all references to function-like macros that are alone on a line, have
# an all uppercase name, and do not end with a semicolon. Such function macros
# are typically used for boiler-plate code, and will confuse the parser if not
# removed.
# The default value is: YES.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.

SKIP_FUNCTION_MACROS   = NO

#---------------------------------------------------------------------------
# Configuration options related to external references
#---------------------------------------------------------------------------

# The TAGFILES tag can be used to specify one or more tag files. For each tag
# file the location of the external documentation should be added. The format of
# a tag file without this location is as follows:
# TAGFILES = file1 file2 ...
# Adding location for the tag files is done as follows:
# TAGFILES = file1=loc1 "file2 = loc2" ...
# where loc1 and loc2 can be relative or absolute paths or URLs. See the
# section "Linking to external documentation" for more information about the use
# of tag files.
# Note: Each tag file must have a unique name (where the name does NOT include
# the path). If a tag file is not located in the directory in which doxygen is
# run, you must also specify the path to the tagfile here.

TAGFILES               =

# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
# tag file that is based on the input files it reads. See section "Linking to
# external documentation" for more information about the usage of tag files.

GENERATE_TAGFILE       =

# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
# the class index. If set to NO, only the inherited external classes will be
# listed.
# The default value is: NO.

ALLEXTERNALS           = NO

# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
# in the modules index. If set to NO, only the current project's groups will be
# listed.
# The default value is: YES.

EXTERNAL_GROUPS        = YES

# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
# the related pages index. If set to NO, only the current project's pages will
# be listed.
# The default value is: YES.

EXTERNAL_PAGES         = YES

#---------------------------------------------------------------------------
# Configuration options related to the dot tool
#---------------------------------------------------------------------------

# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
# NO turns the diagrams off. Note that this option also works with HAVE_DOT
# disabled, but it is recommended to install and use dot, since it yields more
# powerful graphs.
# The default value is: YES.

CLASS_DIAGRAMS         = YES

# You can include diagrams made with dia in doxygen documentation. Doxygen will
# then run dia to produce the diagram and insert it in the documentation. The
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
# If left empty dia is assumed to be found in the default search path.

DIA_PATH               =

# If set to YES the inheritance and collaboration graphs will hide inheritance
# and usage relations if the target is undocumented or is not a class.
# The default value is: YES.

HIDE_UNDOC_RELATIONS   = YES

# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
# available from the path. This tool is part of Graphviz (see:
# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
# Bell Labs. The other options in this section have no effect if this option is
# set to NO
# The default value is: YES.

HAVE_DOT               = @DOXYGEN_HAVE_DOT@

# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
# to run in parallel. When set to 0 doxygen will base this on the number of
# processors available in the system. You can set it explicitly to a value
# larger than 0 to get control over the balance between CPU load and processing
# speed.
# Minimum value: 0, maximum value: 32, default value: 0.
# This tag requires that the tag HAVE_DOT is set to YES.

DOT_NUM_THREADS        = 0

# When you want a differently looking font in the dot files that doxygen
# generates you can specify the font name using DOT_FONTNAME. You need to make
# sure dot is able to find the font, which can be done by putting it in a
# standard location or by setting the DOTFONTPATH environment variable or by
# setting DOT_FONTPATH to the directory containing the font.
# The default value is: Helvetica.
# This tag requires that the tag HAVE_DOT is set to YES.

DOT_FONTNAME           =

# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
# dot graphs.
# Minimum value: 4, maximum value: 24, default value: 10.
# This tag requires that the tag HAVE_DOT is set to YES.

DOT_FONTSIZE           = 10

# By default doxygen will tell dot to use the default font as specified with
# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
# the path where dot can find it using this tag.
# This tag requires that the tag HAVE_DOT is set to YES.

DOT_FONTPATH           =

# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
# each documented class showing the direct and indirect inheritance relations.
# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.

CLASS_GRAPH            = YES

# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
# graph for each documented class showing the direct and indirect implementation
# dependencies (inheritance, containment, and class references variables) of the
# class with other documented classes.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.

COLLABORATION_GRAPH    = YES

# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
# groups, showing the direct groups dependencies.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.

GROUP_GRAPHS           = YES

# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
# collaboration diagrams in a style similar to the OMG's Unified Modeling
# Language.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.

UML_LOOK               = YES

# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
# class node. If there are many fields or methods and many nodes the graph may
# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
# number of items for each type to make the size more manageable. Set this to 0
# for no limit. Note that the threshold may be exceeded by 50% before the limit
# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
# but if the number exceeds 15, the total amount of fields shown is limited to
# 10.
# Minimum value: 0, maximum value: 100, default value: 10.
# This tag requires that the tag HAVE_DOT is set to YES.

UML_LIMIT_NUM_FIELDS   = 10

# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
# collaboration graphs will show the relations between templates and their
# instances.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.

TEMPLATE_RELATIONS     = YES

# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
# YES then doxygen will generate a graph for each documented file showing the
# direct and indirect include dependencies of the file with other documented
# files.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.

INCLUDE_GRAPH          = YES

# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
# set to YES then doxygen will generate a graph for each documented file showing
# the direct and indirect include dependencies of the file with other documented
# files.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.

INCLUDED_BY_GRAPH      = YES

# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
# dependency graph for every global function or class method.
#
# Note that enabling this option will significantly increase the time of a run.
# So in most cases it will be better to enable call graphs for selected
# functions only using the \callgraph command. Disabling a call graph can be
# accomplished by means of the command \hidecallgraph.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.

CALL_GRAPH             = NO

# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
# dependency graph for every global function or class method.
#
# Note that enabling this option will significantly increase the time of a run.
# So in most cases it will be better to enable caller graphs for selected
# functions only using the \callergraph command. Disabling a caller graph can be
# accomplished by means of the command \hidecallergraph.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.

CALLER_GRAPH           = NO

# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
# hierarchy of all classes instead of a textual one.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.

GRAPHICAL_HIERARCHY    = YES

# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
# dependencies a directory has on other directories in a graphical way. The
# dependency relations are determined by the #include relations between the
# files in the directories.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.

DIRECTORY_GRAPH        = YES

# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
# generated by dot. For an explanation of the image formats see the section
# output formats in the documentation of the dot tool (Graphviz (see:
# http://www.graphviz.org/)).
# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
# to make the SVG files visible in IE 9+ (other browsers do not have this
# requirement).
# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,
# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,
# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo,
# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
# png:gdiplus:gdiplus.
# The default value is: png.
# This tag requires that the tag HAVE_DOT is set to YES.

DOT_IMAGE_FORMAT       = svg

# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
# enable generation of interactive SVG images that allow zooming and panning.
#
# Note that this requires a modern browser other than Internet Explorer. Tested
# and working are Firefox, Chrome, Safari, and Opera.
# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
# the SVG files visible. Older versions of IE do not have SVG support.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.

INTERACTIVE_SVG        = YES

# The DOT_PATH tag can be used to specify the path where the dot tool can be
# found. If left blank, it is assumed the dot tool can be found in the path.
# This tag requires that the tag HAVE_DOT is set to YES.

DOT_PATH               =

# The DOTFILE_DIRS tag can be used to specify one or more directories that
# contain dot files that are included in the documentation (see the \dotfile
# command).
# This tag requires that the tag HAVE_DOT is set to YES.

DOTFILE_DIRS           =

# The MSCFILE_DIRS tag can be used to specify one or more directories that
# contain msc files that are included in the documentation (see the \mscfile
# command).

MSCFILE_DIRS           =

# The DIAFILE_DIRS tag can be used to specify one or more directories that
# contain dia files that are included in the documentation (see the \diafile
# command).

DIAFILE_DIRS           =

# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
# path where java can find the plantuml.jar file. If left blank, it is assumed
# PlantUML is not used or called during a preprocessing step. Doxygen will
# generate a warning when it encounters a \startuml command in this case and
# will not generate output for the diagram.

PLANTUML_JAR_PATH      =

# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
# configuration file for plantuml.

PLANTUML_CFG_FILE      =

# When using plantuml, the specified paths are searched for files specified by
# the !include statement in a plantuml block.

PLANTUML_INCLUDE_PATH  =

# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
# that will be shown in the graph. If the number of nodes in a graph becomes
# larger than this value, doxygen will truncate the graph, which is visualized
# by representing a node as a red box. Note that doxygen if the number of direct
# children of the root node in a graph is already larger than
# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
# Minimum value: 0, maximum value: 10000, default value: 50.
# This tag requires that the tag HAVE_DOT is set to YES.

DOT_GRAPH_MAX_NODES    = 50

# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
# generated by dot. A depth value of 3 means that only nodes reachable from the
# root by following a path via at most 3 edges will be shown. Nodes that lay
# further from the root node will be omitted. Note that setting this option to 1
# or 2 may greatly reduce the computation time needed for large code bases. Also
# note that the size of a graph can be further restricted by
# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
# Minimum value: 0, maximum value: 1000, default value: 0.
# This tag requires that the tag HAVE_DOT is set to YES.

MAX_DOT_GRAPH_DEPTH    = 0

# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
# background. This is disabled by default, because dot on Windows does not seem
# to support this out of the box.
#
# Warning: Depending on the platform used, enabling this option may lead to
# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
# read).
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.

DOT_TRANSPARENT        = NO

# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
# files in one run (i.e. multiple -o and -T options on the command line). This
# makes dot run faster, but since only newer versions of dot (>1.8.10) support
# this, this feature is disabled by default.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.

DOT_MULTI_TARGETS      = NO

# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
# explaining the meaning of the various boxes and arrows in the dot generated
# graphs.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.

GENERATE_LEGEND        = YES

# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
# files that are used to generate the various graphs.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.

DOT_CLEANUP            = YES
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted doc/Makefile.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/make -f
include ../subdir-inc.make

.NOTPARALLEL: # stop (make doc upload) from racing

OUT_PREFIX := libfossil-API-
clean-doxy:
	-rm -fr $(OUT_PREFIX)*

DISTCLEAN_FILES += Doxygen
clean: clean-doxy

Doxyfile: Doxyfile.in
	@echo "ERROR: need to reconfigure to create Doxyfile!"; \
	exit 1

doc doxy: Doxyfile
	@echo "Generating API docs..."
	@doxygen
	@echo "Output is in: "; ls -1td $(OUT_PREFIX)*

all:
	@echo "Run 'make doc' to generate the API docs with doxygen."

upload:
	cd libfossil-API-html || exit $$?; \
	rsync -rzq -e ssh --delete . wh:www.f/doxygen/libfossil/
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






















































Deleted doc/db-udf.md.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# Fossil DB User-defined Functions

The Fossil DB schemas can be perused, in the form of commented SQL, in [](/dir/sql).

The library reserves the db symbol prefixes "fsl_" and "fx_fsl_"
(case-insensitive) for its own use - clients should not define any
functions or tables with those name prefixes. Fossil(1) reserves *all*
table names which do not start with "fx_" ("fossil extension"). During
a rebuild, fossil(1) will *drop* any repo tables it does not know
about unless their names start with "fx_".

A libfossil-bound DB handle gets several SQL-callable functions
(UDFs - User-defined Functions) for working with repository state, as
listed below in alphabetical order...

-----

## `FSL_CKOUT_DIR()`

`FSL_CKOUT_DIR([INTEGER=1])` returns the path to the current checkout directory, or NULL if no checkout is opened. If passed no argument or passed a value which evaluates to non-0 in an integer context, the trailing slash is included in the returned value (which is Fossil's historical convention with regard to directory names). If passed any value which evaluates to integer 0, the slash is not included.

e.g. to get the path to the ".fossil-settings" (versionable settings) directory for the current checkout:

```
SELECT FSL_CKOUT_DIR() || '.fossil-settings';
```


## `FSL_CI_MTIME()`

`FSL_CI_MTIME(INT,INT)` takes two RIDs as arguments: the manifest (checkin) version RID and the blob.rid value of a file which part of the first RID's checkin.

It behaves like `fsl_mtime_of_manifest_file()`, returning the calculated (and highly synthetic!) mtime as an SQL integer (Unix epoch timestamp). This is primarily
for internal use.


## `FSL_CONTENT()`

`FSL_CONTENT(INTEGER|STRING)` returns the undeltified,
uncompressed content for the blob record with the given RID (if
the argument is an integer) or symbolic name (as per
`fsl_sym_to_rid()`). If the argument does not resolve to an
in-repo blob, a db-level error is triggered. If passed an
integer, no validation is done on its validity, but such
checking can be enforced by instead passing the the RID as
a string in the form "rid:THE_RID".


## `FSL_DIRPART()`

`FSL_DIRPART(STRING[, BOOL=0])` behaves like
`fsl_file_dirpart()`, returning the result as a string
unless it is empty, in which case the result is an
SQL NULL. If passed a truthy second argument then a trailing
slash is added to the result, else the result will have
no trailing slash.

An example of getting all directory names in the repository (across all
file versions, for simplicity):

```
SELECT DISTINCT(fsl_dirpart(name)) n
FROM filename WHERE n IS NOT NULL
ORDER BY n
```

To get all the dirs for a specific version one needs to do more work. We'll leave that as an exercise for... me, and once i figure it out i'll post it. It seems that getting that information requires C-level code for the time being.

## `FSL_IS_ENQUEUED()` and `FSL_IF_ENQUEUED()`

`FSL_IS_ENQUEUED(INT)` determines whether a given file is "enqueued" in a pending checkin operation. This is normally only used internally, but "might" have some uses elsewhere. If no files have explicitly been queued up for checkin (via the `fsl_checkin_file_enqueue()` C function) then *all files* are considered to be selected (though only *modified* files would actually be checked in if a commit were made).

As its argument it expects a `vfile.id` field value (`vfile` is the table where fossil tracks the current checkout's status). It returns a truthy value if that file is selected/enqueued, else a falsy value.

`FSL_IF_ENQUEUED(INT,X,Y)` is a close counterpart of `FSL_IS_ENQUEUED()`. If the `vfile.id` passed as the first parameter is enqueued then it resolves to the `X` value, else to the `Y` value, *unless* `Y` is `NULL`, in which case it always resolves to `X`. Why? Because its only intended usage is to be passed the `(id, pathname, origname)` fields from the `vfile` table.

`FSL_IF_ENQUEUED(I,X,Y)` is basically equivalent to this pseudocode:

```
result = FSL_IS_ENQUEUED(I) ? X : ((Y IS NULL) ? X : Y)
```

## `FSL_J2U()`

`FSL_J2U(JULIAN_DAY)` expects a Julian Day value and returns its equivalent in Unix Epoch timestamp as a 64-bit integer, as per `fsl_julian_to_unix()`. Fossil tends to use Julian Days for recording timestamps, but a small few cases use Unix timestamps.


## `FSL_MATCH_VFILE_OR_DIR(p1, p2)`


A helper for resolving expressions like:

```
 WHERE pathname='X' C OR
       (pathname>'X/' C AND pathname<'X0' C)
```

i.e. is X a match for the LHS or is X a directory prefix of
LHS?

The C part is functionally equivalent to empty or `COLLATE NOCASE`,
depending on the case-sensitivity setting of the `fsl_cx` instance.

Resolves to NULL if either argument is NULL, 0 if the comparison shown
above is false, 1 if the comparison is an exact match, or 2 if p2 is a
directory prefix part of p1.

It requires that both of its arguments be canonicalized paths with no
extraneous slashes (including no trailing slash).

Examples:

```
select fsl_match_vfile_or_dir('a/b/c','a/b/c');
==> 1

select fsl_match_vfile_or_dir('a/b/c','a');
==> 2

select fsl_match_vfile_or_dir('a/b/c','a/');
==> 0 because of trailing slash on 2nd arg!

select fsl_match_vfile_or_dir('a/b/c',NULL)'
==> NULL
```

This function gets its name from being used exclusively (as of this
writing) for figuring out whether a user-provided name (the 2nd
argument) matches, or is a directory prefix of, the `vfile.pathname`
or `vfile.origname` db fields.

## `FSL_SYM2RID()`

`FSL_SYM2RID(STRING)` returns a blob RID for the given symbol, as per `fsl_sym_to_rid()`. Triggers an SQL error if `fsl_sym_to_rid()` fails.

TODO: add an optional boolean second param (default=true) which tells
it to return NULL instead of triggering an error.

## `FSL_USER()`

Returns the current value of `fsl_cx_user_get()`, or `NULL` if that is
not set.

Example:

```
# f-query -e 'select fsl_user()'
fsl_user()
stephan

# f-query -e 'select fsl_user()' --user root
fsl_user()
root
```

## `NOW()`

Returns the current time as an integer, as per `time(2)`.
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




























































































































































































































































































































Changes to fnc/Makefile.in.

1
2
3
4
5
6
7
8
9
10
11
12
13

14



15

16

17



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32


33
34
35
36
37
38
39

40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
all:
OBJ.DIR := .# KLUDGE for libfossil-specific hack in deps generator
include ../subdir-inc.make
#$(error $(TOP_INCDIR))
#CPPFLAGS += -I$(TOP_INCDIR)
CPPFLAGS += -I$(TOP_DIR)/include
CPPFLAGS += -I$(TOP_DIR)/src# workaround for in-tree sqlite3.h
#INCLUDES_PATH ?= $(HOME)/include /usr/local/include /usr/include

LIBFOSSIL.LDFLAGS := -L.. -L../src -lfossil
ifeq (0,@LIBFOSSIL_SHARED@)
  # In static build, we need some extra flags...
  LIBFOSSIL.LDFLAGS += $(LDFLAGS_MODULE_LOADER) -lpthread -lz

endif





# To build fnc, we need the curses and pthread libraries; not sure what

# this requires on Linux, but the following covers OpenBSD and macOS.



PLATFORM := $(shell sh -c 'uname 2>/dev/null || echo Unknown')
ifneq ($(filter $(PLATFORM),Darwin OpenBSD),)
  CPPFLAGS += -D_XOPEN_SOURCE_EXTENDED
  LIBFOSSIL.LDFLAGS += -lpanel -lcurses -lutil -lz -lpthread
endif

TEST_BINS_LDFLAGS := $(LIBFOSSIL.LDFLAGS) $(EXTRA_LIBS) @SH_LINKFLAGS@

TEST_BINS_LIBDEPS := $(wildcard ../libfossil.*)
ifneq (,$($(TEST_BINS_LIBDEPS)))
ifeq (,$(strip $(filter distclean clean,$(MAKECMDGOALS))))
$(TEST_BINS_LIBDEPS):
	$(MAKE) -C ..
endif
endif



########################################################################
# INC_SEARCH: $(call)able function. $(1) should be the the name of
# a C header file to search for under $(INCLUDES_PATH).
define INC_SEARCH
$(call ShakeNMake.CALL.FIND_FILE,$(1),$(INCLUDES_PATH))
endef


########################################################################
# Sets up binary build rules for binary named $1. This only works
# for the basic test binaries which all have common compile/link
# bits. Assumes sources are in $1.c.
define RULES_F_BIN
$(1).BIN.OBJECTS += $(1).o
$(1).BIN.LDFLAGS += $$(TEST_BINS_LDFLAGS)
$(call ShakeNMake.CALL.RULES.BIN,$(1))
$$($(1).BIN): $$(TEST_BINS_LIBDEPS)
all: $$($(1).BIN)
CLEAN_BINS += $$($(1).BIN)
endef
F_BINS := \
	fnc

test.BIN.LDFLAGS += -lz

$(foreach fbin,$(F_BINS),$(eval $(call RULES_F_BIN,$(fbin))))


<

<
<
<
<
<
|
<
|
<
<
>

>
>
>

>
|
>
|
>
>
>

|
|
<
<
<
<
<
<
<
<
<
<

<
>
>

<
|
<
<
<
|
>

<
<
<
<
<
|
|
|
<
<
<
<
<
|
<
<
<
<
<
1

2





3

4


5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20










21

22
23
24

25



26
27
28





29
30
31





32





all:

include ../subdir-inc.make





LIB_CURSES := @LIB_CURSES@

ifeq (,$(LIB_CURSES))


$(error Expecting LIB_CURSES to be set by the configure process.)
endif
CFLAGS_CURSES := @CFLAGS_CURSES@
LIBF_DIR := $(TOP_DIR)/src
CPPFLAGS += -I$(LIBF_DIR) $(CFLAGS_CURSES)

########################################################################
# To build fnc, we need the curses and pthread libraries.
# -lz is for libfossil
# -liconv is for libfossil on macOS
# -lutil is... ???
# -lm is for sqlite3
FNC_LDFLAGS := $(LIBF_DIR)/libfossil.o $(LIBF_DIR)/sqlite3.o
PLATFORM := $(shell sh -c 'uname 2>/dev/null || echo Unknown')
ifneq ($(filter $(PLATFORM),Darwin),)
  FNC_LDFLAGS += -liconv










endif

FNC_LDFLAGS += -lm
FNC_LDFLAGS += $(LIB_CURSES) -lutil -lz -lpthread


# This is needed for Mac builds, else they don't see the



# wide-char curses APIs:
CPPFLAGS += -D_XOPEN_SOURCE_EXTENDED






fnc.BIN.OBJECTS += fnc.o
fnc.BIN.LDFLAGS := $(FNC_LDFLAGS)
$(eval $(call ShakeNMake.CALL.RULES.BIN,fnc))





all: fnc.BIN





Added fnc/fnc.1.



















































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
.\"
.\" Copyright (c) 2021 Mark Jamsek <mark@jamsek.com>
.\"
.\" Permission to use, copy, modify, and distribute this software for any
.\" purpose with or without fee is hereby granted, provided that the above
.\" copyright notice and this permission notice appear in all copies.
.\"
.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.Dd $Mdocdate$
.Dt FNC 1
.Os
.Sh NAME
.Nm fnc
.Nd Read-only ncurses-based Fossil repository browser
.Sh SYNOPSIS
.Nm
.Op Ar command
.Op Fl h | -help
.Nm
.Op Fl h | -help
.Op Fl v | -version
.Nm
.Cm timeline
.Op Fl z
.Op Fl T Ar tag
.Op Fl b Ar branch
.Op Fl c Ar hash
.Op Fl n Ar number
.Op Fl t Ar type
.Op Fl u Ar user
.Nm
.Cm diff
.Op Fl ciw
.Op Fl x Ar number
.Ar artifact1
.Ar artifact2
.Nm
.Cm blame
.Op Fl c Ar hash
.Ar file
.Sh DESCRIPTION
.Nm
is an interactive read-only browser for
.Xr fossil 1
repositories,
and supports multiple views to display repository data:
.Bl -tag -width Ds
.It Timeline view
Display commits from the repository's history in chronologically
descending order.
.Br
If no
.Ar command
or
.Ar arg
are specified,
.Nm
will default to displaying this view.
.It Diff view
Display changes introduced in the specified commit.
.It Blame view
Display historical attribution of each line in the specified file.
.El
.Pp
.Nm
provides both global and command-specific options and in-app key
bindings.  Global options are specified absent a command name, and
are as follows:
.Bl -tag -width 6v
.It Fl h , -help
Display program help and usage information then exit.
.It Fl v , -version
Display program version then exit.
.El
.Pp
Note that any global options preceding a command name will be
interpreted as the command-specific variant if such an option exists.
.Pp
Global key bindings are as follows:
.Bl -tag -width Ds
.It Cm Tab
Switch focus between open views.
.It Cm f
Toggle the active view between fullscreen and splitscreen mode.  Note
that
.Nm
will open nested views in splitscreen mode if the terminal window is
equal to or greater than 120 columns wide.
.It Cm Q
Immediatey quit
.Nm .
.It Cm q
Quit the active view.
.El
.Pp
Commands available to
.Nm
are as follows:
.Bl -tag -width 4v
.It Cm timeline Oo Fl T | -tag Ar tag Oc Oo Fl b | -branch Ar branch Oc \
Oo Fl c | -commit Ar hash Oc Oo Fl h | -help Oc Oo Fl n | -limit Ar n Oc \
Oo Fl t | -type Ar type Oc Oo Fl u | -username Ar user Oc Oo Fl z | -utc Oc
.Pp
Display commit history of a repository.  This command must be executed
from within or below the repository root; that is,
.Nm
assumes a local
checkout is open in or above the current working directory.
.Pp
If no command is explicitly specified, this command will be executed by
default.
.Pp
Options for
.Cm fnc timeline
are as follows:
.Bl -tag -width Ds
.It Fl T , -tag Ar tag
Only display commits with T cards containing
.Ar tag .
By default,
.Nm
will indiscriminately display all commits irrespective of which T cards
are attached to the commit manifest.
.It Fl b , branch Ar branch
Display commits that are members of the specified
.Ar branch .
The expected argument is the symbolic name of a branch. By default,
.Nm
will display all commits irrespective of the branch on which they
reside.
.It Fl c , commit Ar hash
Open the timeline from the commit identified by
.Ar hash .
The expected argument is either the name of a branch, which will resolve
to the latest commit on the given branch, or (a unique abbreviated
prefix of) a valid commit UUID SHA1 or SHA3 hash. When this option is
not supplied,
.Nm
will open the timeline to the latest leaf on the trunk of the repository
tree.  Note that this option also accepts
.Lk https://fossil-scm.org/home/doc/trunk/www/checkin_names.wiki \
"Fossil's Special Tags".
.It Fl h , -help
Display timeline command help and usage information then exit.
.It Fl n , -limit Ar n
Limit timeline to the
latest
.Ar n
commits.
By default,
.Nm
will load the entire history of the repository's local checkout.
Negative values are a no-op.
.It Fl t , -type Ar type
Only display
.Ar type
commits. Valid
.Ar type
values are as follows:
.Bl -column YXZ description
.Sy ci Ta check-in
.Sy w Ta wiki
.Sy t Ta ticket
.Sy e Ta technote
.Sy f Ta forum post
.El
.Pp
By default, when this option is not supplied,
.Nm
will indiscriminately load all commits irrespective of
.Ar type .
Note that this is a repeatable flag.
.It Fl u , -username Ar user
Only display commits authored by
.Ar user .
.It Fl z , -utc
Use Coordinated Universal Time (UTC) rather than local time when
displaying commit dates and timestamps.
.El
.Pp
Key bindings for
.Cm fnc timeline
are as follows:
.Bl -tag -width Ds
.It Cm Arrow-down, j, >, \&.
Move selection cursor down the timeline.
.It Cm Arrow-up, k, <, \&,
Move selection cursor up the timeline.
.It Cm Ctrl+f, Page-down
Move selection cursor one page down the timeline.
.It Cm Ctrl+b, Page-up
Move selection cursor one page up the timeline.
.It Cm Enter, Space
Open a
.Cm diff
view displaying the changeset of the currently selected commit.
.It Cm /
Prompt to enter a search term to begin searching for commits matching
the pattern provided.  The search term is an extended regular expression,
which is cross-referenced against a commit's comment, the username of
its author, branch, and UUID SHA1 or SHA3 hash.  See
.Xr re_format 7
for regular expression syntax.
.It Cm n
Find the next commit that matches the current search term.  The search
will continue until either a match is found or the earliest commit on
the timeline is consumed.
.It Cm N
Find the previous commit that matches the current search term.  The
search will continue until either a match is found or the latest commit
on the timeline is consumed.
.El
.It Cm diff Oo Fl c | -no-colour Oc Oo Fl h | -help Oc Oo Fl i | -invert \
Oc Oo Fl w | -whitespace Oc Oo Fl x | -context Ar n Oc \
Ar artifact1 Ar artifact2
Display the differences between two repository artifacts.  Both
.Ar artifact1
and
.Ar artifact2
must be of the same type, which is expected to be either (a unique
abbreviated prefix of) a file or commit artifact UUID SHA1 or SHA3 hash,
a symbolic branch name, or an ISO 8601 formatted date.
.Pp
Options for
.Cm fnc diff
are as follows:
.Bl -tag -width Ds
.It Fl c , -no-colour
Disable coloured diff output, which is enabled by default on supported
terminals. If this option is not used, colour can be toggled with the
.Sy c
diff view key binding as documented below.
.It Fl h , -help
Display diff command help and usage information then exit.
.It Fl i , -invert
Invert the difference between artifacts when displaying the diff.
.It Fl w , -whitespace
Ignore whitespace-only changes when displaying the diff.
.It Fl x , -context Ar n
Set
.Ar n
context lines to be shown in the diff.  By default, 5 context lines are
shown.
.El
.Pp
Key bindings for
.Cm fnc diff
are as follows:
.Bl -tag -width Ds
.It Cm c
Toggle coloured diff output.
.Nm
will default to displaying changes and diff metadata in colour.
.It Cm i
Toggle inversion of diff output.
.It Cm v
Toggle verbosity of diff output. By default,
.Nm
will display the entire content of newly added or deleted files.
.It Cm w
Toggle display of whitespace-only changes in diff output.
.It Cm Arrow-down, j
Scroll down one line of diff output.
.It Cm Arrow-up, k
Scroll up one line of diff output.
.It Cm Ctrl+f, Page-down
Scroll down one page of diff output.
.It Cm Ctrl+b, Page-up
Scroll up one page of diff output.
.It Cm \&-, \&_
Decrease the number of context lines shown in diff output.
.It Cm \&=, \&+
Increase the number of context lines shown in diff output.
.It Cm Ctrl+k, K, <, \&,
Move up the
.Cm timeline
to the previous (i.e., more recent) commit and display its diff.
.It Cm Ctrl+j, J, >, \&.
Move down the
.Cm timeline
to the next (i.e., earlier) commit and display its diff.
.It Cm /
Prompt to enter a search term to begin searching the diff output for
lines matching the pattern provided.  The search term is an extended
regular expression, which is documented in
.Xr re_format 7 .
.It Cm n
Find the next line that matches the current search term.
.It Cm N
Find the previous line that matches the current search term.
.El
.It Cm blame Oo Fl c | -commit Ar hash Oc Ar file
Show commit attribution history for each line of the specified
.Ar file .
.Pp
Key bindings for
.Cm fnc blame
are as follows:
.Bl -tag -width Ds
.It Cm NYI
Not yet implemented.
.El
.Pp
Options for
.Cm fnc blame
are as follows:
.Bl -tag -width Ds
.It Fl c , -commit Ar hash
Open the timeline from the commit identified by
.Ar hash .
The expected argument is either the name of a branch, which will resolve
to the latest commit on the given branch, or (a unique abbreviated
prefix of) a valid commit UUID SHA1 or SHA3 hash.
.El
.El
.Sh EXIT STATUS
.Ex -std fnc
.Sh SEE ALSO
.Xr fossil 1 ,
.Xr re_format 7
.Sh AUTHOR
.An Mark Jamsek Aq Mt mark@jamsek.com

Changes to fnc/fnc.c.

11
12
13
14
15
16
17
18


















19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

62

63
64
65
66
67
68
69
70
71
72
73
74
75
76
77












78
79
80
81
82
83
84
85
86
87
88
89
90
91

92
93
94
95
96
97
98
99
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */



















#include <sys/queue.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/socket.h>

#ifdef _WIN32
 #include <windows.h>
 #define ssleep(x) Sleep(x)
#else
 #define ssleep(x) usleep((x) * 1000)
#endif
#include <ctype.h>
#include <curses.h>
#include <panel.h>
#include <locale.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <getopt.h>
#include <string.h>
#include <err.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <libgen.h>
#include <regex.h>
#include <signal.h>
#include <wchar.h>
#include <langinfo.h>

#include "fossil-scm/fossil-cli.h"
#include "fossil-scm/fossil-core.h"
#include "fossil-scm/fossil-db.h"
#include "fossil-scm/fossil-internal.h"
#include "fossil-scm/fossil-util.h"

#define FNC_VERSION	0.1a

/* Utility macros. */
#define MIN(a, b)	(((a) < (b)) ? (a) : (b))
#define MAX(a, b)	(((a) > (b)) ? (a) : (b))

#define CTRL(key)	((key) & 037)	/* CTRL+<key> input. */

#define nitems(a)	(sizeof((a)) / sizeof((a)[0]))
#define STRINGIFYOUT(s)	#s
#define STRINGIFY(s)	STRINGIFYOUT(s)

/* Application macros. */
#define PRINT_VERSION	STRINGIFY(FNC_VERSION)
#define DIFF_MAX_CTXT	64		/* Max diff context lines. */
#define SPIN_INTERVAL	200		/* Status line progress indicator. */
#define SPINNER		"\\|/-\0"

/* Portability macros. */
#ifdef __OpenBSD__
#define strtol(s, p, b)	strtonum(s, INT_MIN, INT_MAX, (const char **)p)
#endif













__dead void		 usage(void);
static const char	*usage_timeline(void);
static const char	*usage_diff(void);
static const char	*usage_blame(void);
static int		 fcli_flag_type_arg_cb(fcli_cliflag const *);
static int		 cmd_timeline(fcli_command const *);
static int		 cmd_diff(fcli_command const *);
static int		 cmd_blame(fcli_command const *);

/*
 * Singleton initialising global configuration and state for app startup.
 */
static struct fnc_setup {
	/* Global options. */

	bool		 err;		/* Indicate fnc error state. */
	bool		 hflag;		/* Flag if --help is requested. */
	bool		 vflag;		/* Flag if --version is requested. */

	/* Timeline options. */
	struct artifact_types {
		const char	**values;
		short		  nitems;








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|


|
|

|








<





<







<
<
|
<
<






>

>















>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
|
|
|
|






>
|







11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

55
56
57
58
59

60
61
62
63
64
65
66


67


68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * This _POSIX_C_SOURCE bit really belongs in a config.h, but in the
 * name of expedience...
 */
#if defined __linux__
#  if !defined(_XOPEN_SOURCE)
#    define _XOPEN_SOURCE 700
/*
 * _POSIX_C_SOURCE >= 199309L needed for sigaction(), sigemptyset() on Linux,
 * but glibc docs claim that _XOPEN_SOURCE>=700 has the same effect, PLUS
 * we need _XOPEN_SOURCE>=500 for ncurses wide-char APIs on linux.
 */
#  endif
#  if !defined(_DEFAULT_SOURCE)
#    define _DEFAULT_SOURCE
/* Needed for strsep() on glibc >= 2.19. */
#  endif
#endif


#include <sys/queue.h>
#include <sys/ioctl.h>

#ifdef _WIN32
#include <windows.h>
#define ssleep(x) Sleep(x)
#else
#define ssleep(x) usleep((x) * 1000)
#endif
#include <ctype.h>
#include <curses.h>
#include <panel.h>
#include <locale.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>

#include <string.h>
#include <err.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>

#include <pthread.h>
#include <libgen.h>
#include <regex.h>
#include <signal.h>
#include <wchar.h>
#include <langinfo.h>



#include "libfossil.h"



#define FNC_VERSION	0.1a

/* Utility macros. */
#define MIN(a, b)	(((a) < (b)) ? (a) : (b))
#define MAX(a, b)	(((a) > (b)) ? (a) : (b))
#if !defined(CTRL)
#define CTRL(key)	((key) & 037)	/* CTRL+<key> input. */
#endif
#define nitems(a)	(sizeof((a)) / sizeof((a)[0]))
#define STRINGIFYOUT(s)	#s
#define STRINGIFY(s)	STRINGIFYOUT(s)

/* Application macros. */
#define PRINT_VERSION	STRINGIFY(FNC_VERSION)
#define DIFF_MAX_CTXT	64		/* Max diff context lines. */
#define SPIN_INTERVAL	200		/* Status line progress indicator. */
#define SPINNER		"\\|/-\0"

/* Portability macros. */
#ifdef __OpenBSD__
#define strtol(s, p, b)	strtonum(s, INT_MIN, INT_MAX, (const char **)p)
#endif

#if !defined(__dead)
#define __dead
#endif

#ifndef TAILQ_FOREACH_SAFE
/* Rewrite of OpenBSD 6.9 sys/queue.h for Linux builds. */
#define TAILQ_FOREACH_SAFE(var, head, field, tmp)			\
	for ((var) = ((head)->tqh_first);				\
		(var) != (NULL) && ((tmp) = TAILQ_NEXT(var, field), 1);	\
		(var) = (tmp))
#endif

__dead static void	usage(void);
static void		usage_timeline(void);
static void		usage_diff(void);
static void		usage_blame(void);
static int		fcli_flag_type_arg_cb(fcli_cliflag const *);
static int		cmd_timeline(fcli_command const *);
static int		cmd_diff(fcli_command const *);
static int		cmd_blame(fcli_command const *);

/*
 * Singleton initialising global configuration and state for app startup.
 */
static struct fnc_setup {
	/* Global options. */
	const char	*cmdarg;	/* Retain argv[1] for use/err report. */
	int		 err;		/* Indicate fnc error state. */
	bool		 hflag;		/* Flag if --help is requested. */
	bool		 vflag;		/* Flag if --version is requested. */

	/* Timeline options. */
	struct artifact_types {
		const char	**values;
		short		  nitems;
109
110
111
112
113
114
115
116
117
118
119



120
121
122
123
124
125
126
127
128
129
130
131
132

133
134
135
136
137
138
139
140
141
142
143
144
145
146
147



148
149
150
151
152
153
154
	const char	*filter_tag;	/* Only load commits with <tag>. */
	const char	*filter_branch;	/* Only load commits from <branch>. */
	const char	*filter_user;	/* Only load commits from <user>. */
	const char	*filter_type;	/* Placeholder for repeatable types. */
	bool		 utc;		/* Display UTC sans user local time. */

	/* Diff options. */
	const char	*artifact0;	/* First diff (required) argument. */
	const char	*artifact1;	/* Second diff (required) argument. */
	short		 context;	/* Number of context lines. */
	bool		 ws;		/* Ignore whitespace-only changes. */




	/* Blame options. */
	const char	*path;		/* Show blame of REQUIRED <path> arg. */

	/* Command line flags and help. */
	fcli_help_info	   fnc_help;			/* Global help. */
	fcli_cliflag	   cliflags_global[3];		/* Global options. */
	fcli_command	   cmd_args[4];			/* App commands. */
	const char	*(*fnc_usage_cb[3])(void);	/* Command usage. */
	fcli_cliflag	   cliflags_timeline[10];	/* Timeline options. */
	fcli_cliflag	   cliflags_diff[5];		/* Diff options. */
	fcli_cliflag	   cliflags_blame[2];		/* Blame options. */
} fnc_init = {

	false,		/* err fnc error state. */
	false,		/* hflag if --help is requested. */
	false,		/* vflag if --version is requested. */
	NULL,		/* filter_types defaults to indiscriminate. */
	{0},		/* nrecords defaults to all commits. */
	{0},		/* start_commit defaults to latest leaf. */
	NULL,		/* filter_tag defaults to indiscriminate. */
	NULL,		/* filter_branch defaults to indiscriminate. */
	NULL,		/* filter_user defaults to indiscriminate. */
	NULL,		/* filter_type temporary placeholder. */
	false,		/* utc defaults to off (i.e., show user local time). */
	NULL,		/* artifact0 diff command first required argument. */
	NULL,		/* artifact1 diff command second required argument. */
	5,		/* context defaults to five context lines. */
	false,		/* ws defaults to acknowledge whitespace. */



	NULL,		/* path blame command required argument. */

	{ /* fnc_help global app help details. */
	    "A read-only ncurses browser for Fossil repositories in the "
	    "terminal.", NULL, usage
	},








<
<
|

>
>
>





|
|
|
|
|
|
|

>
|










<
<
|

>
>
>







136
137
138
139
140
141
142


143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172


173
174
175
176
177
178
179
180
181
182
183
184
	const char	*filter_tag;	/* Only load commits with <tag>. */
	const char	*filter_branch;	/* Only load commits from <branch>. */
	const char	*filter_user;	/* Only load commits from <user>. */
	const char	*filter_type;	/* Placeholder for repeatable types. */
	bool		 utc;		/* Display UTC sans user local time. */

	/* Diff options. */


	const char	*context;	/* Number of context lines. */
	bool		 ws;		/* Ignore whitespace-only changes. */
	bool		 nocolour;	/* Disable colour in diff output. */
	bool		 quiet;		/* Disable verbose diff output. */
	bool		 invert;	/* Toggle inverted diff output. */

	/* Blame options. */
	const char	*path;		/* Show blame of REQUIRED <path> arg. */

	/* Command line flags and help. */
	fcli_help_info	  fnc_help;			/* Global help. */
	fcli_cliflag	  cliflags_global[3];		/* Global options. */
	fcli_command	  cmd_args[4];			/* App commands. */
	void		(*fnc_usage_cb[3])(void);	/* Command usage. */
	fcli_cliflag	  cliflags_timeline[10];	/* Timeline options. */
	fcli_cliflag	  cliflags_diff[7];		/* Diff options. */
	fcli_cliflag	  cliflags_blame[2];		/* Blame options. */
} fnc_init = {
	NULL,		/* cmdarg copy of argv[1] to aid usage/error report. */
	0,		/* err fnc error state. */
	false,		/* hflag if --help is requested. */
	false,		/* vflag if --version is requested. */
	NULL,		/* filter_types defaults to indiscriminate. */
	{0},		/* nrecords defaults to all commits. */
	{0},		/* start_commit defaults to latest leaf. */
	NULL,		/* filter_tag defaults to indiscriminate. */
	NULL,		/* filter_branch defaults to indiscriminate. */
	NULL,		/* filter_user defaults to indiscriminate. */
	NULL,		/* filter_type temporary placeholder. */
	false,		/* utc defaults to off (i.e., show user local time). */


	NULL,		/* context defaults to five context lines. */
	false,		/* ws defaults to acknowledge whitespace. */
	false,		/* nocolour defaults to off (i.e., use diff colours). */
	false,		/* quiet defaults to off (i.e., verbose diff is on). */
	false,		/* invert diff defaults to off. */
	NULL,		/* path blame command required argument. */

	{ /* fnc_help global app help details. */
	    "A read-only ncurses browser for Fossil repositories in the "
	    "terminal.", NULL, usage
	},

201
202
203
204
205
206
207




208
209
210
211





212
213

214
215

216
217
218
219
220
221
222
223
224





225
226
227
228
229
230
231
            "Only display commits authored by <username>."),
	    FCLI_FLAG_BOOL("z", "utc", &fnc_init.utc,
            "Use UTC (instead of local) time."),
	    fcli_cliflag_empty_m
	}, /* End cliflags_timeline. */

	{ /* cliflags_diff diff command related options. */




	    FCLI_FLAG_BOOL("h", "help", NULL,
            "Display diff command help and usage."),
	    FCLI_FLAG_BOOL("i", "invert", NULL,
            "Invert difference between artifacts."),





	    FCLI_FLAG_BOOL("w", "whitespace", &fnc_init.ws,
            "Ignore whitespace-only changes when displaying diff."),

	    FCLI_FLAG("x", "context", "<n>", &fnc_init.context,
            "Show <n> context lines when displaying diff."),

	    fcli_cliflag_empty_m
	}, /* End cliflags_diff. */

	{ /* cliflags_blame blame command related options. */
	    FCLI_FLAG_BOOL("h", "help", NULL,
            "Display blame command help and usage."),
	    fcli_cliflag_empty_m
	}, /* End cliflags_blame. */
};






enum date_string {
	ISO8601_DATE_ONLY = 10,
	ISO8601_TIMESTAMP = 20
};

enum fnc_file_chng_stat {







>
>
>
>


|
|
>
>
>
>
>

|
>

|
>









>
>
>
>
>







231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
            "Only display commits authored by <username>."),
	    FCLI_FLAG_BOOL("z", "utc", &fnc_init.utc,
            "Use UTC (instead of local) time."),
	    fcli_cliflag_empty_m
	}, /* End cliflags_timeline. */

	{ /* cliflags_diff diff command related options. */
	    FCLI_FLAG_BOOL("c", "no-colour", &fnc_init.nocolour,
            "Disable coloured diff output, which is enabled by default on\n    "
            "supported terminals. Colour can also be toggled with the 'c' "
            "\n    key binding in diff view when this option is not used."),
	    FCLI_FLAG_BOOL("h", "help", NULL,
            "Display diff command help and usage."),
	    FCLI_FLAG_BOOL("i", "invert", &fnc_init.invert,
            "Invert difference between artifacts. Inversion can also be "
            "toggled\n    with the 'i' key binding in diff view."),
	    FCLI_FLAG_BOOL("q", "quiet", &fnc_init.quiet,
            "Disable verbose diff output; that is, do not output complete"
            " content\n    of newly added or deleted files. Verbosity can also"
            " be toggled with\n    the 'v' key binding in diff view."),
	    FCLI_FLAG_BOOL("w", "whitespace", &fnc_init.ws,
            "Ignore whitespace-only changes when displaying diff. This option "
            "can\n    also be toggled with the 'w' key binding in diff view."),
	    FCLI_FLAG("x", "context", "<n>", &fnc_init.context,
            "Show <n> context lines when displaying diff; <n> is capped at 64."
            "\n    Negative values are a no-op."),
	    fcli_cliflag_empty_m
	}, /* End cliflags_diff. */

	{ /* cliflags_blame blame command related options. */
	    FCLI_FLAG_BOOL("h", "help", NULL,
            "Display blame command help and usage."),
	    fcli_cliflag_empty_m
	}, /* End cliflags_blame. */
};

enum fsl_list_object {
	FNC_ARTIFACT_OBJ,
	FNC_COLOUR_OBJ
};

enum date_string {
	ISO8601_DATE_ONLY = 10,
	ISO8601_TIMESTAMP = 20
};

enum fnc_file_chng_stat {
249
250
251
252
253
254
255
















256
257
258
259
260
261
262

enum fnc_search_state {
	SEARCH_WAITING,
	SEARCH_CONTINUE,
	SEARCH_COMPLETE,
	SEARCH_NO_MATCH
};

















struct fnc_commit_artifact {
	fsl_buffer	 wiki;
	fsl_buffer	 pwiki;
	fsl_list	 changeset;
	fsl_uuid_str	 uuid;
	fsl_uuid_str	 puuid;







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324

enum fnc_search_state {
	SEARCH_WAITING,
	SEARCH_CONTINUE,
	SEARCH_COMPLETE,
	SEARCH_NO_MATCH
};

enum diff_colours {
	FNC_DIFF_META = 1,
	FNC_DIFF_MINUS,
	FNC_DIFF_PLUS,
	FNC_DIFF_CHNK,
};

struct fnc_colour {
	regex_t	regex;
	uint8_t	scheme;
};

struct fsl_list_state {
	enum fsl_list_object	obj;
};

struct fnc_commit_artifact {
	fsl_buffer	 wiki;
	fsl_buffer	 pwiki;
	fsl_list	 changeset;
	fsl_uuid_str	 uuid;
	fsl_uuid_str	 puuid;
318
319
320
321
322
323
324

325
326
327
328
329
330
331
332

333
334
335
336
337
338
339
340
341
342
343
344
345
	pthread_t		 thread_id;
};

struct fnc_diff_view_state {
	struct fnc_view			*timeline_view;
	struct fnc_commit_artifact	*selected_commit;
	fsl_buffer			 buf;

	FILE				*f;
	int				 first_line_onscreen;
	int				 last_line_onscreen;
	int				 diff_flags;
	int				 context;
	int				 sbs;
	int				 matched_line;
	int				 current_line;

	size_t				 nlines;
	fpos_t				*line_offsets;
	bool				 eof;
	bool				 ignore_ws;
	bool				 invert;
	bool				 verbose;
};

TAILQ_HEAD(view_tailhead, fnc_view);
struct fnc_view {
	TAILQ_ENTRY(fnc_view)	 entries;
	WINDOW			*window;
	PANEL			*panel;







>








>

|

<
|
<







380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399

400

401
402
403
404
405
406
407
	pthread_t		 thread_id;
};

struct fnc_diff_view_state {
	struct fnc_view			*timeline_view;
	struct fnc_commit_artifact	*selected_commit;
	fsl_buffer			 buf;
	fsl_list			 colours;
	FILE				*f;
	int				 first_line_onscreen;
	int				 last_line_onscreen;
	int				 diff_flags;
	int				 context;
	int				 sbs;
	int				 matched_line;
	int				 current_line;
	size_t				 ncols;
	size_t				 nlines;
	off_t				*line_offsets;
	bool				 eof;

	bool				 colour;

};

TAILQ_HEAD(view_tailhead, fnc_view);
struct fnc_view {
	TAILQ_ENTRY(fnc_view)	 entries;
	WINDOW			*window;
	PANEL			*panel;
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
static int		 view_loop(struct fnc_view *);
static int		 show_timeline_view(struct fnc_view *);
static void		*tl_producer_thread(void *);
static int		 block_main_thread_signals(void);
static int		 build_commits(struct fnc_tl_thread_cx *);
static int		 signal_tl_thread(struct fnc_view *, int);
static int		 draw_commits(struct fnc_view *);
static char		*parse_emailaddr_username(char *);
static int		 formatln(wchar_t **, int *, const char *, int, int);
static int		 multibyte_to_wchar(const char *, wchar_t **, size_t *);
static int		 write_commit_line(struct fnc_view *,
			    struct fnc_commit_artifact *, int);
static int		 view_input(struct fnc_view **, int *,
			    struct fnc_view *, struct view_tailhead *);
static int		 tl_input_handler(struct fnc_view **, struct fnc_view *,







|







446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
static int		 view_loop(struct fnc_view *);
static int		 show_timeline_view(struct fnc_view *);
static void		*tl_producer_thread(void *);
static int		 block_main_thread_signals(void);
static int		 build_commits(struct fnc_tl_thread_cx *);
static int		 signal_tl_thread(struct fnc_view *, int);
static int		 draw_commits(struct fnc_view *);
static void		 parse_emailaddr_username(char **);
static int		 formatln(wchar_t **, int *, const char *, int, int);
static int		 multibyte_to_wchar(const char *, wchar_t **, size_t *);
static int		 write_commit_line(struct fnc_view *,
			    struct fnc_commit_artifact *, int);
static int		 view_input(struct fnc_view **, int *,
			    struct fnc_view *, struct view_tailhead *);
static int		 tl_input_handler(struct fnc_view **, struct fnc_view *,
410
411
412
413
414
415
416

417
418
419
420


421
422
423
424
425
426
427
428
429
430
431
432
433
434
435

436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471


472
473
474
475


476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
static bool		 find_commit_match(struct fnc_commit_artifact *,
			    regex_t *);
static int		 init_diff_commit(struct fnc_view **, int,
			    struct fnc_commit_artifact *, struct fnc_view *);
static int		 open_diff_view(struct fnc_view *,
			    struct fnc_commit_artifact *, int, bool, bool, bool,
			    struct fnc_view *);

static void		 show_diff_status(struct fnc_view *);
static int		 create_diff(struct fnc_diff_view_state *);
static int		 create_changeset(struct fnc_commit_artifact *);
static int		 write_commit_meta(struct fnc_diff_view_state *);


static int		 add_line_offset(fpos_t **, size_t *, fpos_t);
static int		 diff_commit(fsl_buffer *, struct fnc_commit_artifact *,
			    int, bool, int, int);
static int		 diff_wiki(fsl_buffer *, struct fnc_commit_artifact *,
			    int, int, int);
static int		 verbose_diff(void *, void *);
static int		 diff_file_artifact(fsl_buffer *, fsl_id_t,
			    fsl_card_F const *, fsl_id_t, fsl_card_F const *,
			    int, bool, int, int);
static int		 fsl_ckout_file_content(fsl_cx *, char const *,
			    fsl_buffer *);
static int		 fsl_ckout_mtime(fsl_cx *, fsl_id_t, fsl_card_F const *,
			    fsl_time_t *, fsl_time_t *);
static int		 show_diff(struct fnc_view *);
static int		 write_diff(struct fnc_view *, const char *);

static int		 write_matched_line(int *, const char *, int, int,
			    WINDOW *, regmatch_t *);
static void		 draw_vborder(struct fnc_view *);
static int		 diff_input_handler(struct fnc_view **,
			    struct fnc_view *, int);
static int		 set_selected_commit(struct fnc_diff_view_state *,
			    struct commit_entry *);
static int		 diff_search_init(struct fnc_view *);
static int		 diff_search_next(struct fnc_view *);
static int		 view_close(struct fnc_view *);
static int		 close_timeline_view(struct fnc_view *);
static int		 close_diff_view(struct fnc_view *);
static int		 view_resize(struct fnc_view *);
static int		 screen_is_split(struct fnc_view *);
static bool		 screen_is_shared(struct fnc_view *);
static void		 fnc_resizeterm(void);
static int		 join_tl_thread(struct fnc_tl_view_state *);
static void		 fnc_free_commits(struct commit_queue *);
static void		 fnc_commit_artifact_close(struct fnc_commit_artifact*);
static int		 fsl_list_object_close(void *, void *);
static void		 sigwinch_handler(int);
static void		 sigpipe_handler(int);
static void		 sigcont_handler(int);

int
main(int argc, char * const * argv)
{
	fcli_command	*cmd = NULL;
	fsl_cx		*f = NULL;
	fsl_error	 e = fsl_error_empty;	/* DEBUG */
	int		 rc = 0;

	fnc_init.filter_types =
	    (struct artifact_types *)fsl_malloc(sizeof(struct artifact_types));
	fnc_init.filter_types->values = fsl_malloc(sizeof(char *));
	fnc_init.filter_types->nitems = 0;



	if (!setlocale(LC_CTYPE, ""))
		fsl_fprintf(stderr, "[!] Warning: Can't set locale.\n");



	fcli.cliFlags = fnc_init.cliflags_global;
	fcli.appHelp = &fnc_init.fnc_help;
	rc = fcli_setup(argc, (char const * const *)argv);
	if (rc)
		goto end;

	if (fnc_init.vflag) {
		fnc_show_version();
		goto end;
	} else if (fnc_init.hflag) {
		usage();
		goto end;
	}

	if (argc == 1)
		cmd = &fnc_init.cmd_args[FNC_VIEW_TIMELINE];
	else if (((rc = fcli_dispatch_commands(fnc_init.cmd_args, true)
	    == FSL_RC_NOT_FOUND)) || (rc = fcli_has_unused_args(false))) {
		fnc_init.err = true;
		usage();
		goto end;
	} else if (rc)
		goto end;


	rc = fcli_fingerprint_check(true);
	if (rc)
		goto end;

	f = fcli_cx();
	if (!fsl_cx_db_repo(f)) {







>




>
>
|

|
|
|
<


|






>



















|





|










>
>




>
>


|






|

<
<



|

|

<


<







472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490

491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555


556
557
558
559
560
561
562

563
564

565
566
567
568
569
570
571
static bool		 find_commit_match(struct fnc_commit_artifact *,
			    regex_t *);
static int		 init_diff_commit(struct fnc_view **, int,
			    struct fnc_commit_artifact *, struct fnc_view *);
static int		 open_diff_view(struct fnc_view *,
			    struct fnc_commit_artifact *, int, bool, bool, bool,
			    struct fnc_view *);
static int		 set_diff_colours(fsl_list *);
static void		 show_diff_status(struct fnc_view *);
static int		 create_diff(struct fnc_diff_view_state *);
static int		 create_changeset(struct fnc_commit_artifact *);
static int		 write_commit_meta(struct fnc_diff_view_state *);
static int		 wrapline(char *, fsl_size_t ncols_avail,
			    struct fnc_diff_view_state *, off_t *);
static int		 add_line_offset(off_t **, size_t *, off_t);
static int		 diff_commit(fsl_buffer *, struct fnc_commit_artifact *,
			    int, int, int);
static int		 diff_non_checkin(fsl_buffer *, struct
			    fnc_commit_artifact *, int, int, int);

static int		 diff_file_artifact(fsl_buffer *, fsl_id_t,
			    fsl_card_F const *, fsl_id_t, fsl_card_F const *,
			    int, int, int);
static int		 fsl_ckout_file_content(fsl_cx *, char const *,
			    fsl_buffer *);
static int		 fsl_ckout_mtime(fsl_cx *, fsl_id_t, fsl_card_F const *,
			    fsl_time_t *, fsl_time_t *);
static int		 show_diff(struct fnc_view *);
static int		 write_diff(struct fnc_view *, const char *);
static int		 match_line(const void *, const void *);
static int		 write_matched_line(int *, const char *, int, int,
			    WINDOW *, regmatch_t *);
static void		 draw_vborder(struct fnc_view *);
static int		 diff_input_handler(struct fnc_view **,
			    struct fnc_view *, int);
static int		 set_selected_commit(struct fnc_diff_view_state *,
			    struct commit_entry *);
static int		 diff_search_init(struct fnc_view *);
static int		 diff_search_next(struct fnc_view *);
static int		 view_close(struct fnc_view *);
static int		 close_timeline_view(struct fnc_view *);
static int		 close_diff_view(struct fnc_view *);
static int		 view_resize(struct fnc_view *);
static int		 screen_is_split(struct fnc_view *);
static bool		 screen_is_shared(struct fnc_view *);
static void		 fnc_resizeterm(void);
static int		 join_tl_thread(struct fnc_tl_view_state *);
static void		 fnc_free_commits(struct commit_queue *);
static void		 fnc_commit_artifact_close(struct fnc_commit_artifact*);
static int		 fsl_list_object_free(void *, void *);
static void		 sigwinch_handler(int);
static void		 sigpipe_handler(int);
static void		 sigcont_handler(int);

int
main(int argc, const char **argv)
{
	fcli_command	*cmd = NULL;
	fsl_cx		*f = NULL;
	fsl_error	 e = fsl_error_empty;	/* DEBUG */
	int		 rc = 0;

	fnc_init.filter_types =
	    (struct artifact_types *)fsl_malloc(sizeof(struct artifact_types));
	fnc_init.filter_types->values = fsl_malloc(sizeof(char *));
	fnc_init.filter_types->nitems = 0;
	fcli_fax(fnc_init.filter_types->values);
	fcli_fax(fnc_init.filter_types);

	if (!setlocale(LC_CTYPE, ""))
		fsl_fprintf(stderr, "[!] Warning: Can't set locale.\n");

	fnc_init.cmdarg = argv[1];	/* Which cmd to show usage if needed. */
	fcli.clientFlags.verbose = 2;	/* Verbose error reporting. */
	fcli.cliFlags = fnc_init.cliflags_global;
	fcli.appHelp = &fnc_init.fnc_help;
	rc = fcli_setup(argc, argv);
	if (rc)
		goto end;

	if (fnc_init.vflag) {
		fnc_show_version();
		goto end;
	} else if (fnc_init.hflag)
		usage();



	if (argc == 1)
		cmd = &fnc_init.cmd_args[FNC_VIEW_TIMELINE];
	else if (((rc = fcli_dispatch_commands(fnc_init.cmd_args, false)
	    == FSL_RC_NOT_FOUND)) || (rc = fcli_has_unused_args(false))) {
		fnc_init.err = rc;
		usage();

	} else if (rc)
		goto end;


	rc = fcli_fingerprint_check(true);
	if (rc)
		goto end;

	f = fcli_cx();
	if (!fsl_cx_db_repo(f)) {
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
	endwin();
	putchar('\n');
	if (rc) {
		if (rc == FCLI_RC_HELP)
			rc = 0;
		else {
			fsl_error_set(&e, rc, NULL);
			fsl_fprintf(stderr, "%s: %s %d\n", getprogname(),
			    fsl_rc_cstr(rc), rc);
			fsl_fprintf(stderr, "-> %s\n", e.msg.mem);
		}
	}
	return fcli_end_of_main(rc);
}








|







579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
	endwin();
	putchar('\n');
	if (rc) {
		if (rc == FCLI_RC_HELP)
			rc = 0;
		else {
			fsl_error_set(&e, rc, NULL);
			fsl_fprintf(stderr, "%s: %s %d\n", fcli_progname(),
			    fsl_rc_cstr(rc), rc);
			fsl_fprintf(stderr, "-> %s\n", e.msg.mem);
		}
	}
	return fcli_end_of_main(rc);
}

557
558
559
560
561
562
563
564


565
566
567
568
569
570
571
	}

	if (fnc_init.start_commit.uuid != NULL) {
		rid = fsl_uuid_to_rid(f, fnc_init.start_commit.uuid);
		if (rid > 0)
			fnc_init.start_commit.rid = rid;
		else
			return rc;


	}

	rc = init_curses();
	if (rc)
		return fsl_cx_err_set(f, fsl_errno_to_rc(rc, FSL_RC_ERROR),
		    "can not initialise ncurses");
	v = view_open(0, 0, 0, 0, FNC_VIEW_TIMELINE);







|
>
>







622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
	}

	if (fnc_init.start_commit.uuid != NULL) {
		rid = fsl_uuid_to_rid(f, fnc_init.start_commit.uuid);
		if (rid > 0)
			fnc_init.start_commit.rid = rid;
		else
			return fcli_err_set(FSL_RC_TYPE,
			    "artifact [%s] not resolvable to a commit",
			    fnc_init.start_commit.uuid);
	}

	rc = init_curses();
	if (rc)
		return fsl_cx_err_set(f, fsl_errno_to_rc(rc, FSL_RC_ERROR),
		    "can not initialise ncurses");
	v = view_open(0, 0, 0, 0, FNC_VIEW_TIMELINE);
581
582
583
584
585
586
587
588
589
590
591
592
593








594
595
596
597
598
599
600
static int
init_curses(void)
{
	int rc = 0;

	initscr();
	cbreak();
	halfdelay(1);
	noecho();
	nonl();
	intrflush(stdscr, FALSE);
	keypad(stdscr, TRUE);
	curs_set(0);









	if ((rc = sigaction(SIGPIPE,
	    &(struct sigaction){{sigpipe_handler}}, NULL)))
		return rc;
	if ((rc = sigaction(SIGWINCH,
	    &(struct sigaction){{sigwinch_handler}}, NULL)))
		return rc;







<





>
>
>
>
>
>
>
>







648
649
650
651
652
653
654

655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
static int
init_curses(void)
{
	int rc = 0;

	initscr();
	cbreak();

	noecho();
	nonl();
	intrflush(stdscr, FALSE);
	keypad(stdscr, TRUE);
	curs_set(0);
#ifndef __linux__
	typeahead(-1);	/* Don't disrupt screen update operations. */
#endif

	if (!fnc_init.nocolour && has_colors()) {
		start_color();
		use_default_colors();
	}

	if ((rc = sigaction(SIGPIPE,
	    &(struct sigaction){{sigpipe_handler}}, NULL)))
		return rc;
	if ((rc = sigaction(SIGWINCH,
	    &(struct sigaction){{sigwinch_handler}}, NULL)))
		return rc;
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
	    "AND tagxref.tagtype > 0) as tags, "
	    /*6*/"coalesce(ecomment, comment) AS comment FROM event JOIN blob "
	    "WHERE blob.rid=event.objid", fnc_init.utc ? "" : ", 'localtime'");

	if (fnc_init.filter_types->nitems) {
		fsl_buffer_appendf(&sql, " AND (");
		for (idx = 0; idx < fnc_init.filter_types->nitems; ++idx) {
			fsl_buffer_appendf(&sql, " eventtype='%s'%s",
			    fnc_init.filter_types->values[idx], (idx + 1) <
			    fnc_init.filter_types->nitems ? " OR " : ")");
			/* This produces a double-free? */
			/* fsl_free((char *)fnc_init.filter_types->values[idx]); */
			fnc_init.filter_types->values[idx] = NULL;
		}
		fsl_free(fnc_init.filter_types->values);







|







756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
	    "AND tagxref.tagtype > 0) as tags, "
	    /*6*/"coalesce(ecomment, comment) AS comment FROM event JOIN blob "
	    "WHERE blob.rid=event.objid", fnc_init.utc ? "" : ", 'localtime'");

	if (fnc_init.filter_types->nitems) {
		fsl_buffer_appendf(&sql, " AND (");
		for (idx = 0; idx < fnc_init.filter_types->nitems; ++idx) {
			fsl_buffer_appendf(&sql, " eventtype=%Q%s",
			    fnc_init.filter_types->values[idx], (idx + 1) <
			    fnc_init.filter_types->nitems ? " OR " : ")");
			/* This produces a double-free? */
			/* fsl_free((char *)fnc_init.filter_types->values[idx]); */
			fnc_init.filter_types->values[idx] = NULL;
		}
		fsl_free(fnc_init.filter_types->values);
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817

static int
view_loop(struct fnc_view *view)
{
	struct view_tailhead	 views;
	struct fnc_view		*new_view;
	fsl_cx			*f = fcli_cx();
	int			 fast_refresh = 10;
	int			 done = 0, rc = 0;

	if ((rc = pthread_mutex_lock(&fnc_mutex)))
		return fsl_cx_err_set(f, rc, "mutex lock");

	TAILQ_INIT(&views);
	TAILQ_INSERT_HEAD(&views, view, entries);

	view->active = true;
	rc = view->show(view);
	if (rc)
		return rc;
	update_panels();
	doupdate();
	while (!TAILQ_EMPTY(&views) && !done && !rec_sigpipe) {
		/* Refresh fast during initialisation, then become slower. */
		if (fast_refresh && fast_refresh-- == 0)
			halfdelay(10);	/* Switch to once per second. */

		rc = view_input(&new_view, &done, view, &views);
		if (rc)
			break;
		if (view->egress) {
			struct fnc_view *v, *prev = NULL;

			if (view_is_parent(view))







<












|
<

<
<
<
<







858
859
860
861
862
863
864

865
866
867
868
869
870
871
872
873
874
875
876
877

878




879
880
881
882
883
884
885

static int
view_loop(struct fnc_view *view)
{
	struct view_tailhead	 views;
	struct fnc_view		*new_view;
	fsl_cx			*f = fcli_cx();

	int			 done = 0, rc = 0;

	if ((rc = pthread_mutex_lock(&fnc_mutex)))
		return fsl_cx_err_set(f, rc, "mutex lock");

	TAILQ_INIT(&views);
	TAILQ_INSERT_HEAD(&views, view, entries);

	view->active = true;
	rc = view->show(view);
	if (rc)
		return rc;


	while (!TAILQ_EMPTY(&views) && !done && !rec_sigpipe) {




		rc = view_input(&new_view, &done, view, &views);
		if (rc)
			break;
		if (view->egress) {
			struct fnc_view *v, *prev = NULL;

			if (view_is_parent(view))
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
				if (v->active)
					break;
			}
			if (view == NULL && new_view == NULL) {
				/* No view is active; try to pick one. */
				if (prev)
					view = prev;
				else if (!TAILQ_EMPTY(&views)) {
					view = TAILQ_LAST(&views,
					view_tailhead);
				}
				if (view) {
					if (view->focus_child) {
						view->child->active = true;
						view = view->child;
					} else
						view->active = true;
				}







|

|
<







902
903
904
905
906
907
908
909
910
911

912
913
914
915
916
917
918
				if (v->active)
					break;
			}
			if (view == NULL && new_view == NULL) {
				/* No view is active; try to pick one. */
				if (prev)
					view = prev;
				else if (!TAILQ_EMPTY(&views))
					view = TAILQ_LAST(&views,
					    view_tailhead);

				if (view) {
					if (view->focus_child) {
						view->child->active = true;
						view = view->child;
					} else
						view->active = true;
				}
883
884
885
886
887
888
889



890
891
892



893

894
895
896
897
898
899
900
					goto end;
			}
			rc = view->show(view);
			if (rc)
				goto end;
			if (view->child) {
				rc = view->child->show(view->child);



				if (rc)
					goto end;
			}



			update_panels();

			doupdate();
		}
	}
end:
	while (!TAILQ_EMPTY(&views)) {
		view = TAILQ_FIRST(&views);
		TAILQ_REMOVE(&views, view, entries);







>
>
>



>
>
>

>







950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
					goto end;
			}
			rc = view->show(view);
			if (rc)
				goto end;
			if (view->child) {
				rc = view->child->show(view->child);
#ifdef __linux__
				wnoutrefresh(view->child->window);
#endif
				if (rc)
					goto end;
			}
#ifdef __linux__
			wnoutrefresh(view->window);
#else
			update_panels();
#endif
			doupdate();
		}
	}
end:
	while (!TAILQ_EMPTY(&views)) {
		view = TAILQ_FIRST(&views);
		TAILQ_REMOVE(&views, view, entries);
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924

static int
show_timeline_view(struct fnc_view *view)
{
	struct fnc_tl_view_state	*s = &view->state.timeline;
	int				 rc = 0;

	if (s->thread_id == NULL) {
		rc = pthread_create(&s->thread_id, NULL, tl_producer_thread,
		    &s->thread_cx);
		if (rc)
			return fsl_errno_to_rc(errno, rc);
		if (s->thread_cx.ncommits_needed > 0) {
			rc = signal_tl_thread(view, 1);
			if (rc)







|







984
985
986
987
988
989
990
991
992
993
994
995
996
997
998

static int
show_timeline_view(struct fnc_view *view)
{
	struct fnc_tl_view_state	*s = &view->state.timeline;
	int				 rc = 0;

	if (!s->thread_id) {
		rc = pthread_create(&s->thread_id, NULL, tl_producer_thread,
		    &s->thread_cx);
		if (rc)
			return fsl_errno_to_rc(errno, rc);
		if (s->thread_cx.ncommits_needed > 0) {
			rc = signal_tl_thread(view, 1);
			if (rc)
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
static int
signal_tl_thread(struct fnc_view *view, int wait)
{
	struct fnc_tl_thread_cx	*ta = &view->state.timeline.thread_cx;
	fsl_cx			*f = fcli_cx();
	int			 rc = 0;

	halfdelay(1);

	while (ta->ncommits_needed > 0) {
		if (ta->timeline_end)
			break;

		/* Wake timeline thread. */
		if ((rc = pthread_cond_signal(&ta->commit_consumer)))
			return fsl_cx_err_set(f, fsl_errno_to_rc(errno,







<
<







1232
1233
1234
1235
1236
1237
1238


1239
1240
1241
1242
1243
1244
1245
static int
signal_tl_thread(struct fnc_view *view, int wait)
{
	struct fnc_tl_thread_cx	*ta = &view->state.timeline.thread_cx;
	fsl_cx			*f = fcli_cx();
	int			 rc = 0;



	while (ta->ncommits_needed > 0) {
		if (ta->timeline_end)
			break;

		/* Wake timeline thread. */
		if ((rc = pthread_cond_signal(&ta->commit_consumer)))
			return fsl_cx_err_set(f, fsl_errno_to_rc(errno,
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
	if (s->selected_commit && !(view->searching != SEARCH_DONE &&
	    view->search_status == SEARCH_WAITING)) {
		uuid = fsl_strdup(s->selected_commit->commit->uuid);
		branch = fsl_strdup(s->selected_commit->commit->branch);
		type = fsl_strdup(s->selected_commit->commit->type);
	}

	if (!s->thread_cx.ncommits_needed)
		halfdelay(10);

	if (s->thread_cx.ncommits_needed > 0) {
		if ((idxstr = fsl_mprintf(" [%d/%d] %s",
		    entry ? entry->idx + 1 : 0, s->commits.ncommits,
		    (view->searching && !view->search_status) ?
		    "searching..." : "loading...")) == NULL) {
			rc = fsl_cx_err_set(f, FSL_RC_RANGE, "mprintf idx");
			goto end;







<
<
<







1288
1289
1290
1291
1292
1293
1294



1295
1296
1297
1298
1299
1300
1301
	if (s->selected_commit && !(view->searching != SEARCH_DONE &&
	    view->search_status == SEARCH_WAITING)) {
		uuid = fsl_strdup(s->selected_commit->commit->uuid);
		branch = fsl_strdup(s->selected_commit->commit->branch);
		type = fsl_strdup(s->selected_commit->commit->type);
	}




	if (s->thread_cx.ncommits_needed > 0) {
		if ((idxstr = fsl_mprintf(" [%d/%d] %s",
		    entry ? entry->idx + 1 : 0, s->commits.ncommits,
		    (view->searching && !view->search_status) ?
		    "searching..." : "loading...")) == NULL) {
			rc = fsl_cx_err_set(f, FSL_RC_RANGE, "mprintf idx");
			goto end;
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
		if (ncommits >= view->nlines - 1)
			break;
		user = fsl_strdup(entry->commit->user);
		if (user == NULL) {
			rc = fsl_errno_to_rc(errno, FSL_RC_OOM);
			return rc;
		}
		if (strpbrk(user, "<@") != NULL)
			user = parse_emailaddr_username(user);
		rc = formatln(&usr_wcstr, &usrlen, user, view->ncols, 0);
		if (max_usrlen < usrlen)
			max_usrlen = usrlen;
		fsl_free(usr_wcstr);
		fsl_free(user);
		++ncommits;
		entry = TAILQ_NEXT(entry, entries);







|
|







1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
		if (ncommits >= view->nlines - 1)
			break;
		user = fsl_strdup(entry->commit->user);
		if (user == NULL) {
			rc = fsl_errno_to_rc(errno, FSL_RC_OOM);
			return rc;
		}
		if (strpbrk(user, "<@>") != NULL)
			parse_emailaddr_username(&user);
		rc = formatln(&usr_wcstr, &usrlen, user, view->ncols, 0);
		if (max_usrlen < usrlen)
			max_usrlen = usrlen;
		fsl_free(usr_wcstr);
		fsl_free(user);
		++ncommits;
		entry = TAILQ_NEXT(entry, entries);
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346

1347

1348
1349
1350
1351
1352
1353
1354
1355
1356
		rc = write_commit_line(view, entry->commit, max_usrlen);
		if (ncommits == s->selected_idx)
			wstandend(view->window);
		++ncommits;
		s->last_commit_onscreen = entry;
		entry = TAILQ_NEXT(entry, entries);
	}
	update_panels();
	doupdate();

end:
	free(branch);
	free(type);
	free(uuid);
	free(idxstr);
	free(headln);
	return rc;
}

static char *
parse_emailaddr_username(char *username)
{
	char	*lt, *at, *usr;

	if ((lt = strchr(username, '<')) && lt[1] != '\0')
		++lt;
	if ((at = strchr(username, '@')))
		*at = '\0';

	if (lt) {
		usr = fsl_strdup(lt);
		fsl_free(username);

		return usr;

	}
	return username;
}

static int
formatln(wchar_t **ptr, int *wstrlen, const char *mbstr, int column_limit,
    int start_column)
{
	fsl_cx		*f = fcli_cx();







|
<










|
|

|

|
<
<
<
<
|
|
|
>
|
>
|
|







1384
1385
1386
1387
1388
1389
1390
1391

1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407




1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
		rc = write_commit_line(view, entry->commit, max_usrlen);
		if (ncommits == s->selected_idx)
			wstandend(view->window);
		++ncommits;
		s->last_commit_onscreen = entry;
		entry = TAILQ_NEXT(entry, entries);
	}
	draw_vborder(view);


end:
	free(branch);
	free(type);
	free(uuid);
	free(idxstr);
	free(headln);
	return rc;
}

static void
parse_emailaddr_username(char **username)
{
	char	*lt, *usr;

	lt = strchr(*username, '<');




	if (lt && lt[1] != '\0') {
		usr = fsl_strdup(++lt);
		fsl_free(*username);
	} else
		usr = *username;
	usr[strcspn(usr, "@>")] = '\0';

	*username = usr;
}

static int
formatln(wchar_t **ptr, int *wstrlen, const char *mbstr, int column_limit,
    int start_column)
{
	fsl_cx		*f = fcli_cx();
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
 */
static int
write_commit_line(struct fnc_view *view, struct fnc_commit_artifact *commit,
    int max_usrlen)
{
	fsl_cx		*f = fcli_cx();
	wchar_t		*usr_wcstr = NULL, *wcomment = NULL;
	char		*comment = NULL, *date = NULL, *pad = NULL;
	char		*eol = NULL, *user = NULL;
	size_t		 i = 0;
	int		 col_pos, ncols_avail, usrlen, commentlen, rc = 0;

	/* Trim time component from timestamp for the date field. */
	date = fsl_strdup(commit->timestamp);
	while (!fsl_isspace(date[i++])) {}
	date[i] = '\0';







|
|







1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
 */
static int
write_commit_line(struct fnc_view *view, struct fnc_commit_artifact *commit,
    int max_usrlen)
{
	fsl_cx		*f = fcli_cx();
	wchar_t		*usr_wcstr = NULL, *wcomment = NULL;
	char		*comment0 = NULL, *comment = NULL, *date = NULL;
	char		*eol = NULL, *pad = NULL, *user = NULL;
	size_t		 i = 0;
	int		 col_pos, ncols_avail, usrlen, commentlen, rc = 0;

	/* Trim time component from timestamp for the date field. */
	date = fsl_strdup(commit->timestamp);
	while (!fsl_isspace(date[i++])) {}
	date[i] = '\0';
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508

1509
1510
1511
1512
1513
1514
1515
	 * Parse username from emailaddr if needed, and postfix username
	 * with as much whitespace as needed to fill two spaces beyond
	 * the longest username on the screen.
	 */
	user = fsl_strdup(commit->user);
	if (user == NULL)
		goto end;
	if (strpbrk(user, "<@") != NULL)
		 user = parse_emailaddr_username(user);
	rc = formatln(&usr_wcstr, &usrlen, user, view->ncols - col_pos,
	    col_pos);
	if (rc)
		goto end;
	waddwstr(view->window, usr_wcstr);
	pad = fsl_mprintf("%*s",  max_usrlen - usrlen + 2, " ");
	waddstr(view->window, pad);
	col_pos += (max_usrlen + 2);
	if (col_pos > view->ncols)
		goto end;

	/* Only show comment up to the first newline character. */
	comment = strdup(commit->comment);

	if (comment == NULL)
		return fsl_cx_err_set(f, FSL_RC_OOM, "strdup");
	while (*comment == '\n')
		++comment;
	eol = strchr(comment, '\n');
	if (eol)
		*eol = '\0';







|
|












|
>







1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
	 * Parse username from emailaddr if needed, and postfix username
	 * with as much whitespace as needed to fill two spaces beyond
	 * the longest username on the screen.
	 */
	user = fsl_strdup(commit->user);
	if (user == NULL)
		goto end;
	if (strpbrk(user, "<@>") != NULL)
		parse_emailaddr_username(&user);
	rc = formatln(&usr_wcstr, &usrlen, user, view->ncols - col_pos,
	    col_pos);
	if (rc)
		goto end;
	waddwstr(view->window, usr_wcstr);
	pad = fsl_mprintf("%*s",  max_usrlen - usrlen + 2, " ");
	waddstr(view->window, pad);
	col_pos += (max_usrlen + 2);
	if (col_pos > view->ncols)
		goto end;

	/* Only show comment up to the first newline character. */
	comment0 = strdup(commit->comment);
	comment = comment0;
	if (comment == NULL)
		return fsl_cx_err_set(f, FSL_RC_OOM, "strdup");
	while (*comment == '\n')
		++comment;
	eol = strchr(comment, '\n');
	if (eol)
		*eol = '\0';
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
		++col_pos;
	}
end:
	fsl_free(date);
	fsl_free(user);
	fsl_free(usr_wcstr);
	fsl_free(pad);
	fsl_free(comment);
	fsl_free(wcomment);
	return rc;
}

static int
view_input(struct fnc_view **new, int *done, struct fnc_view *view,
    struct view_tailhead *views)







|







1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
		++col_pos;
	}
end:
	fsl_free(date);
	fsl_free(user);
	fsl_free(usr_wcstr);
	fsl_free(pad);
	fsl_free(comment0);
	fsl_free(wcomment);
	return rc;
}

static int
view_input(struct fnc_view **new, int *done, struct fnc_view *view,
    struct view_tailhead *views)
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
	nodelay(stdscr, FALSE);
	/* Allow thread to make progress while waiting for input. */
	if ((rc = pthread_mutex_unlock(&fnc_mutex)))
		return fsl_cx_err_set(f, rc, "mutex unlock");
	ch = wgetch(view->window);
	if ((rc = pthread_mutex_lock(&fnc_mutex)))
		return fsl_cx_err_set(f, rc, "mutex lock");
	nodelay(stdscr, TRUE);

	if (rec_sigwinch || rec_sigcont) {
		fnc_resizeterm();
		rec_sigwinch = 0;
		rec_sigcont = 0;
		TAILQ_FOREACH(v, views, entries) {
			if ((rc = view_resize(v)))







<







1628
1629
1630
1631
1632
1633
1634

1635
1636
1637
1638
1639
1640
1641
	nodelay(stdscr, FALSE);
	/* Allow thread to make progress while waiting for input. */
	if ((rc = pthread_mutex_unlock(&fnc_mutex)))
		return fsl_cx_err_set(f, rc, "mutex unlock");
	ch = wgetch(view->window);
	if ((rc = pthread_mutex_lock(&fnc_mutex)))
		return fsl_cx_err_set(f, rc, "mutex lock");


	if (rec_sigwinch || rec_sigcont) {
		fnc_resizeterm();
		rec_sigwinch = 0;
		rec_sigcont = 0;
		TAILQ_FOREACH(v, views, entries) {
			if ((rc = view_resize(v)))
1949
1950
1951
1952
1953
1954
1955



1956
1957
1958
1959
1960
1961
1962
static int
tl_search_next(struct fnc_view *view)
{
	struct fnc_tl_view_state	*s = &view->state.timeline;
	struct commit_entry		*entry;
	fsl_cx				*f = fcli_cx();
	int				 rc = 0;




	/* Show status update in timeline view. */
	show_timeline_view(view);
	update_panels();
	doupdate();

	if (s->search_commit) {







>
>
>







2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
static int
tl_search_next(struct fnc_view *view)
{
	struct fnc_tl_view_state	*s = &view->state.timeline;
	struct commit_entry		*entry;
	fsl_cx				*f = fcli_cx();
	int				 rc = 0;

	if (!s->thread_cx.ncommits_needed && view->started_search)
		halfdelay(1);

	/* Show status update in timeline view. */
	show_timeline_view(view);
	update_panels();
	doupdate();

	if (s->search_commit) {
1991
1992
1993
1994
1995
1996
1997

1998
1999
2000
2001
2002
2003
2004
	while (1) {
		if (entry == NULL) {
			if (s->thread_cx.timeline_end || view->searching ==
			    SEARCH_REVERSE) {
				view->search_status = (s->matched_commit ==
				    NULL ?  SEARCH_NO_MATCH : SEARCH_COMPLETE);
				s->search_commit = NULL;

				return rc;
			}
			/*
			 * Wake the timeline thread to produce more commits.
			 * Search will resume at s->search_commit upon return.
			 */
			++s->thread_cx.ncommits_needed;







>







2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
	while (1) {
		if (entry == NULL) {
			if (s->thread_cx.timeline_end || view->searching ==
			    SEARCH_REVERSE) {
				view->search_status = (s->matched_commit ==
				    NULL ?  SEARCH_NO_MATCH : SEARCH_COMPLETE);
				s->search_commit = NULL;
				cbreak();
				return rc;
			}
			/*
			 * Wake the timeline thread to produce more commits.
			 * Search will resume at s->search_commit upon return.
			 */
			++s->thread_cx.ncommits_needed;
2029
2030
2031
2032
2033
2034
2035

2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048


2049
2050
2051
2052
2053
2054
2055
			if ((rc = tl_input_handler(NULL, view, KEY_UP)))
				return rc;
			--cur;
		}
	}

	s->search_commit = NULL;


	return rc;
}

static bool
find_commit_match(struct fnc_commit_artifact *commit,
regex_t *regex)
{
	regmatch_t	regmatch;

	if (regexec(regex, commit->user, 1, &regmatch, 0) == 0 ||
	    regexec(regex, (char *)commit->uuid, 1, &regmatch, 0) == 0 ||
	    regexec(regex, commit->comment, 1, &regmatch, 0) == 0)


		return true;

	return false;
}

static int
view_close(struct fnc_view *view)







>












|
>
>







2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
			if ((rc = tl_input_handler(NULL, view, KEY_UP)))
				return rc;
			--cur;
		}
	}

	s->search_commit = NULL;
	cbreak();

	return rc;
}

static bool
find_commit_match(struct fnc_commit_artifact *commit,
regex_t *regex)
{
	regmatch_t	regmatch;

	if (regexec(regex, commit->user, 1, &regmatch, 0) == 0 ||
	    regexec(regex, (char *)commit->uuid, 1, &regmatch, 0) == 0 ||
	    regexec(regex, commit->comment, 1, &regmatch, 0) == 0 ||
	    (commit->branch && regexec(regex, commit->branch, 1, &regmatch, 0)
	     == 0))
		return true;

	return false;
}

static int
view_close(struct fnc_view *view)
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129

		if ((rc = pthread_cond_signal(&s->thread_cx.commit_consumer)))
			return fsl_cx_err_set(f, rc, "pthread_cond_signal");
		if ((rc = pthread_mutex_unlock(&fnc_mutex)))
			return fsl_cx_err_set(f, rc, "mutex unlock fail");
		if ((rc = pthread_join(s->thread_id, &err)) ||
		    err == PTHREAD_CANCELED)
			return fsl_cx_err_set(f, rc ? rc : (int)err,
			    "pthread_join");
		if ((rc = pthread_mutex_lock(&fnc_mutex)))
			return fsl_cx_err_set(f, rc, "mutex lock fail");

		s->thread_id = NULL;
	}

	if ((rc = pthread_cond_destroy(&s->thread_cx.commit_consumer)))
		fsl_cx_err_set(f, rc, "pthread_cond_destroy consumer");

	if ((rc = pthread_cond_destroy(&s->thread_cx.commit_producer)))
		fsl_cx_err_set(f, rc, "pthread_cond_destroy producer");







|




|







2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202

		if ((rc = pthread_cond_signal(&s->thread_cx.commit_consumer)))
			return fsl_cx_err_set(f, rc, "pthread_cond_signal");
		if ((rc = pthread_mutex_unlock(&fnc_mutex)))
			return fsl_cx_err_set(f, rc, "mutex unlock fail");
		if ((rc = pthread_join(s->thread_id, &err)) ||
		    err == PTHREAD_CANCELED)
			return fsl_cx_err_set(f, rc ? rc : (intptr_t)err,
			    "pthread_join");
		if ((rc = pthread_mutex_lock(&fnc_mutex)))
			return fsl_cx_err_set(f, rc, "mutex lock fail");

		s->thread_id = 0;
	}

	if ((rc = pthread_cond_destroy(&s->thread_cx.commit_consumer)))
		fsl_cx_err_set(f, rc, "pthread_cond_destroy consumer");

	if ((rc = pthread_cond_destroy(&s->thread_cx.commit_producer)))
		fsl_cx_err_set(f, rc, "pthread_cond_destroy producer");
2144
2145
2146
2147
2148
2149
2150


2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167



2168
2169
2170
2171
2172












2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215


2216


2217
2218
2219
2220
2221
2222

2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235









































2236
2237
2238
2239



2240

2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271

2272

















2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286

2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
		--commits->ncommits;
	}
}

static void
fnc_commit_artifact_close(struct fnc_commit_artifact *commit)
{


	fsl_free(commit->branch);
	fsl_free(commit->comment);
	fsl_free(commit->timestamp);
	fsl_free(commit->type);
	fsl_free(commit->user);
	fsl_free(commit->uuid);
	fsl_free(commit->puuid);
	fsl_list_clear(&commit->changeset, fsl_list_object_close, NULL);
	fsl_list_reserve(&commit->changeset, 0);
	fsl_free(commit);
}

static int
fsl_list_object_close(void *elem, void *state)
{
	struct fsl_file_artifact *ffa = (struct fsl_file_artifact *)elem;




	if (ffa->fc)
		fsl_free(ffa->fc);

	if (ffa)
		fsl_free(ffa);













	return 0;
}

static int
init_diff_commit(struct fnc_view **new_view, int start_col,
    struct fnc_commit_artifact *commit, struct fnc_view *timeline_view)
{
	struct fnc_view			*diff_view;
	fsl_cx				*f = fcli_cx();
	int				 rc = 0;

	diff_view = view_open(0, 0, 0, start_col, FNC_VIEW_DIFF);
	if (diff_view == NULL)
		return fsl_cx_err_set(f, FSL_RC_OOM, "new_view");

	rc = open_diff_view(diff_view, commit, 5, false, false, true,
	    timeline_view);
	if (!rc)
		*new_view = diff_view;

	return rc;
}

static int
open_diff_view(struct fnc_view *view, struct fnc_commit_artifact *cmt2,
    int context, bool ignore_ws, bool invert, bool verbosity,
    struct fnc_view *timeline_view)
{
	struct fnc_diff_view_state	*s = &view->state.diff;
	int				 rc = 0;

	s->selected_commit = cmt2;
	s->first_line_onscreen = 1;
	s->last_line_onscreen = view->nlines;
	s->current_line = 1;
	s->f = NULL;
	s->context = context;
	s->sbs = 0;
	s->verbose = verbosity;
	s->ignore_ws = ignore_ws;
	s->invert = invert;
	s->timeline_view = timeline_view;





	if (timeline_view && screen_is_split(view))
		show_timeline_view(timeline_view); /* draw vborder */
	show_diff_status(view);

	s->line_offsets = NULL;
	s->nlines = 0;

	rc = create_diff(s);
	if (rc)
		return rc;

	view->show = show_diff;
	view->input = diff_input_handler;
	view->close = close_diff_view;
	view->search_init = diff_search_init;
	view->search_next = diff_search_next;

	return rc;
}










































static void
show_diff_status(struct fnc_view *view)
{
	mvwaddstr(view->window, 0, 0, "generating diff...");



	update_panels();

	doupdate();
}

static int
create_diff(struct fnc_diff_view_state *s)
{
	fsl_cx	*f = fcli_cx();
	FILE	*fout = NULL;
	char	*line, *st = NULL;
	fpos_t	 lnoff = 0;
	int	 n, rc = 0;

	free(s->line_offsets);
	s->line_offsets = fsl_malloc(sizeof(fpos_t));
	if (s->line_offsets == NULL)
		return fsl_cx_err_set(f, FSL_RC_OOM, "fsl_malloc");
	s->nlines = 0;

	fout = tmpfile();
	if (fout == NULL) {
		rc = fsl_cx_err_set(f, fsl_errno_to_rc(errno, FSL_RC_IO),
		    "tmpfile");
		goto end;
	}
	if (s->f && fclose(s->f) == EOF) {
		rc = fsl_cx_err_set(f, fsl_errno_to_rc(errno, FSL_RC_IO),
		    "fclose");
		goto end;
	}
	s->f = fout;


	create_changeset(s->selected_commit);

















	write_commit_meta(s);

	/*
	 * We'll diff artifacts of type "ci" (i.e., "checkin") separately, as
	 * it's a different process to diff the others (wiki, technote, etc.).
	 */
	if (!fsl_strncmp(s->selected_commit->type, "checkin",
	    fsl_strlen(s->selected_commit->type)) &&
	    s->selected_commit->puuid != NULL) {
		diff_commit(&s->buf, s->selected_commit, s->diff_flags,
		    s->verbose, s->context, s->sbs);
	} else
		diff_wiki(&s->buf, s->selected_commit, s->diff_flags,
		    s->context, s->sbs);


	/*
	 * Parse the diff buffer line-by-line to record byte offsets of each
	 * line for scrolling and searching in diff view.
	 */
	st = fsl_strdup(fsl_buffer_str(&s->buf));
	lnoff = (s->line_offsets)[s->nlines -1];
	while ((line = strsep(&st, "\n")) != NULL) {
		n = fsl_fprintf(s->f, "%s\n", line);
		lnoff += n;
		rc = add_line_offset(&s->line_offsets, &s->nlines, lnoff);
		if (rc)
			goto end;
	}







>
>







|





|

|

>
>
>
|
|
<
|
|
>
>
>
>
>
>
>
>
>
>
>
>
















|
|







|






|






|
|
|

>
>

>
>






>













>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>




>
>
>

>









|



|

















>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|









<
<
<

>






|







2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247

2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439



2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
		--commits->ncommits;
	}
}

static void
fnc_commit_artifact_close(struct fnc_commit_artifact *commit)
{
	struct fsl_list_state	st = { FNC_ARTIFACT_OBJ };

	fsl_free(commit->branch);
	fsl_free(commit->comment);
	fsl_free(commit->timestamp);
	fsl_free(commit->type);
	fsl_free(commit->user);
	fsl_free(commit->uuid);
	fsl_free(commit->puuid);
	fsl_list_clear(&commit->changeset, fsl_list_object_free, &st);
	fsl_list_reserve(&commit->changeset, 0);
	fsl_free(commit);
}

static int
fsl_list_object_free(void *elem, void *state)
{
	struct fsl_list_state *st = state;

	switch (st->obj) {
	case FNC_ARTIFACT_OBJ: {
		struct fsl_file_artifact *ffa = elem;
		if (ffa->fc)
			fsl_free(ffa->fc);

		if (ffa)
			fsl_free(ffa);
		break;
	}
	case FNC_COLOUR_OBJ: {
		struct fnc_colour *c = elem;
		regfree(&c->regex);
		fsl_free(c);
		break;
	}
	default:
		return fcli_err_set(FSL_RC_MISSING_INFO,
		    "fsl_list_state.obj missing or invalid: %d", st->obj);
	}

	return 0;
}

static int
init_diff_commit(struct fnc_view **new_view, int start_col,
    struct fnc_commit_artifact *commit, struct fnc_view *timeline_view)
{
	struct fnc_view			*diff_view;
	fsl_cx				*f = fcli_cx();
	int				 rc = 0;

	diff_view = view_open(0, 0, 0, start_col, FNC_VIEW_DIFF);
	if (diff_view == NULL)
		return fsl_cx_err_set(f, FSL_RC_OOM, "new_view");

	rc = open_diff_view(diff_view, commit, 5, fnc_init.ws,
	    fnc_init.invert, !fnc_init.quiet, timeline_view);
	if (!rc)
		*new_view = diff_view;

	return rc;
}

static int
open_diff_view(struct fnc_view *view, struct fnc_commit_artifact *commit,
    int context, bool ignore_ws, bool invert, bool verbosity,
    struct fnc_view *timeline_view)
{
	struct fnc_diff_view_state	*s = &view->state.diff;
	int				 rc = 0;

	s->selected_commit = commit;
	s->first_line_onscreen = 1;
	s->last_line_onscreen = view->nlines;
	s->current_line = 1;
	s->f = NULL;
	s->context = context;
	s->sbs = 0;
	verbosity ? s->diff_flags |= FSL_DIFF_VERBOSE : 0;
	ignore_ws ? s->diff_flags |= FSL_DIFF_IGNORE_ALLWS : 0;
	invert ? s->diff_flags |= FSL_DIFF_INVERT : 0;
	s->timeline_view = timeline_view;
	s->colours = fsl_list_empty;
	s->colour = !fnc_init.nocolour;

	if (s->colour && has_colors())
		set_diff_colours(&s->colours);
	if (timeline_view && screen_is_split(view))
		show_timeline_view(timeline_view); /* draw vborder */
	show_diff_status(view);

	s->line_offsets = NULL;
	s->nlines = 0;
	s->ncols = view->ncols;
	rc = create_diff(s);
	if (rc)
		return rc;

	view->show = show_diff;
	view->input = diff_input_handler;
	view->close = close_diff_view;
	view->search_init = diff_search_init;
	view->search_next = diff_search_next;

	return rc;
}

static int
set_diff_colours(fsl_list *s)
{
	struct fnc_colour	*colour;
	fsl_cx			*f = fcli_cx();
	fsl_size_t		 idx;
	const char		*regexp[4] = {
				    "^((checkin|wiki|ticket|technote) [0-9a-f]|"
				    "hash [+-] |\\[[+~>-]] |[+-]{3} )",
				    "^-", "^\\+", "^@@"
				 };
	int			 pairs[4][2] = {
				    {FNC_DIFF_META, COLOR_GREEN},
				    {FNC_DIFF_MINUS, COLOR_MAGENTA},
				    {FNC_DIFF_PLUS, COLOR_CYAN},
				    {FNC_DIFF_CHNK, COLOR_YELLOW}
				 };
	int			 rc = 0;

	for (idx = 0; idx < nitems(regexp); ++idx) {
		colour = fsl_malloc(sizeof(*colour));
		if (colour == NULL)
			return fsl_cx_err_set(f, FSL_RC_OOM, "malloc");
		rc = regcomp(&colour->regex, regexp[idx],
		    REG_EXTENDED | REG_NEWLINE | REG_NOSUB);
		if (rc) {
			static char regerr[512];
			regerror(rc, &colour->regex, regerr, sizeof(regerr));
			free(colour);
			return fsl_cx_err_set(f, fsl_errno_to_rc(rc,
			    FSL_RC_ERROR), "regcomp: %s [%s]", regerr,
			    regexp[idx]);
		}
		colour->scheme = pairs[idx][0];
		init_pair(colour->scheme, pairs[idx][1], -1);
		fsl_list_append(s, colour);
	}

	return rc;
}

static void
show_diff_status(struct fnc_view *view)
{
	mvwaddstr(view->window, 0, 0, "generating diff...");
#ifdef __linux__
	wnoutrefresh(view->window);
#else
	update_panels();
#endif
	doupdate();
}

static int
create_diff(struct fnc_diff_view_state *s)
{
	fsl_cx	*f = fcli_cx();
	FILE	*fout = NULL;
	char	*line, *st = NULL;
	off_t	 lnoff = 0;
	int	 n, rc = 0;

	free(s->line_offsets);
	s->line_offsets = fsl_malloc(sizeof(off_t));
	if (s->line_offsets == NULL)
		return fsl_cx_err_set(f, FSL_RC_OOM, "fsl_malloc");
	s->nlines = 0;

	fout = tmpfile();
	if (fout == NULL) {
		rc = fsl_cx_err_set(f, fsl_errno_to_rc(errno, FSL_RC_IO),
		    "tmpfile");
		goto end;
	}
	if (s->f && fclose(s->f) == EOF) {
		rc = fsl_cx_err_set(f, fsl_errno_to_rc(errno, FSL_RC_IO),
		    "fclose");
		goto end;
	}
	s->f = fout;

	if (!fsl_strcmp(s->selected_commit->type, "checkin")) {
		rc = create_changeset(s->selected_commit);
		if (rc) {
			rc = fsl_cx_err_set(f, FSL_RC_DB, "create_changeset");
			goto end;
		}
	} else
		diff_non_checkin(&s->buf, s->selected_commit, s->diff_flags,
		    s->context, s->sbs);

	rc = add_line_offset(&s->line_offsets, &s->nlines, 0);
	if (rc)
		goto end;

	/*
	 * If we don't have a timeline view, we arrived here via cmd_diff()
	 * (i.e., 'fnc diff' on the CLI), so don't display commit metadata.
	 */
	if (s->timeline_view)
		write_commit_meta(s);

	/*
	 * We'll diff artifacts of type "ci" (i.e., "checkin") separately, as
	 * it's a different process to diff the others (wiki, technote, etc.).
	 */
	if (!fsl_strncmp(s->selected_commit->type, "checkin",
	    fsl_strlen(s->selected_commit->type)) &&
	    s->selected_commit->puuid != NULL) {
		diff_commit(&s->buf, s->selected_commit, s->diff_flags,



		    s->context, s->sbs);
	}

	/*
	 * Parse the diff buffer line-by-line to record byte offsets of each
	 * line for scrolling and searching in diff view.
	 */
	st = fsl_strdup(fsl_buffer_str(&s->buf));
	lnoff = (s->line_offsets)[s->nlines - 1];
	while ((line = strsep(&st, "\n")) != NULL) {
		n = fsl_fprintf(s->f, "%s\n", line);
		lnoff += n;
		rc = add_line_offset(&s->line_offsets, &s->nlines, lnoff);
		if (rc)
			goto end;
	}
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370




2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
	    "ORDER BY name", commit->rid, commit->rid);
	if (rc)
		return fsl_cx_err_set(f, FSL_RC_DB, "fsl_db_prepare_cached");

	while ((rc = fsl_stmt_step(st)) == FSL_RC_STEP_ROW) {
		struct fsl_file_artifact *fdiff = NULL;
		const char *path, *oldpath, *olduuid, *uuid;
		int perm;

		path = fsl_stmt_g_text(st, 0, NULL);	/* Current filename. */
		perm = fsl_stmt_g_int32(st, 1);		/* File permissions. */
		olduuid = fsl_stmt_g_text(st, 2, NULL);	/* UUID before change */
		uuid = fsl_stmt_g_text(st, 3, NULL);	/* UUID after change. */
		oldpath = fsl_stmt_g_text(st, 4, NULL);	/* Old name, if chngd */

		fdiff = fsl_malloc(sizeof(struct fsl_file_artifact));
		fdiff->fc = fsl_malloc(sizeof(fsl_card_F));
		fdiff->fc->name = fsl_strdup(path);
		if (!uuid) {
			fdiff->fc->uuid = fsl_strdup(olduuid);
			fdiff->change = FILE_DELETED;
		} else if (!olduuid || *olduuid == -1) {
			fdiff->fc->uuid = fsl_strdup(uuid);
			fdiff->change = FILE_ADDED;
		} else if (oldpath) {
			fdiff->fc->uuid = fsl_strdup(uuid);
			fdiff->fc->priorName = fsl_strdup(oldpath);
			fdiff->change = FILE_RENAMED;
		} else {
			fdiff->fc->uuid = fsl_strdup(uuid);
			fdiff->change = FILE_CHANGED;
		}
		fsl_list_append(&changeset, fdiff);
	}

	commit->changeset = changeset;
	fsl_stmt_cached_yield(st);




	return rc;
}

static int
write_commit_meta(struct fnc_diff_view_state *s)
{
	char		*line = NULL, *st = NULL;
	fsl_size_t	 idx;
	fpos_t		 lnoff = 0;
	int		 n, rc = 0;

	rc = add_line_offset(&s->line_offsets, &s->nlines, 0);
	if (rc)
		goto end;

	if ((n = fsl_fprintf(s->f,"%s %s\n", s->selected_commit->type,
	    s->selected_commit->uuid)) < 0)
		goto end;
	lnoff += n;
	if ((rc = add_line_offset(&s->line_offsets, &s->nlines, lnoff)))
		goto end;








|


|










|















>
>
>
>







|
|


<
<
<
<







2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540




2541
2542
2543
2544
2545
2546
2547
	    "ORDER BY name", commit->rid, commit->rid);
	if (rc)
		return fsl_cx_err_set(f, FSL_RC_DB, "fsl_db_prepare_cached");

	while ((rc = fsl_stmt_step(st)) == FSL_RC_STEP_ROW) {
		struct fsl_file_artifact *fdiff = NULL;
		const char *path, *oldpath, *olduuid, *uuid;
		//int perm;

		path = fsl_stmt_g_text(st, 0, NULL);	/* Current filename. */
		//perm = fsl_stmt_g_int32(st, 1);		/* File permissions. */
		olduuid = fsl_stmt_g_text(st, 2, NULL);	/* UUID before change */
		uuid = fsl_stmt_g_text(st, 3, NULL);	/* UUID after change. */
		oldpath = fsl_stmt_g_text(st, 4, NULL);	/* Old name, if chngd */

		fdiff = fsl_malloc(sizeof(struct fsl_file_artifact));
		fdiff->fc = fsl_malloc(sizeof(fsl_card_F));
		fdiff->fc->name = fsl_strdup(path);
		if (!uuid) {
			fdiff->fc->uuid = fsl_strdup(olduuid);
			fdiff->change = FILE_DELETED;
		} else if (!olduuid) {
			fdiff->fc->uuid = fsl_strdup(uuid);
			fdiff->change = FILE_ADDED;
		} else if (oldpath) {
			fdiff->fc->uuid = fsl_strdup(uuid);
			fdiff->fc->priorName = fsl_strdup(oldpath);
			fdiff->change = FILE_RENAMED;
		} else {
			fdiff->fc->uuid = fsl_strdup(uuid);
			fdiff->change = FILE_CHANGED;
		}
		fsl_list_append(&changeset, fdiff);
	}

	commit->changeset = changeset;
	fsl_stmt_cached_yield(st);

	if (rc == FSL_RC_STEP_DONE)
		rc = 0;

	return rc;
}

static int
write_commit_meta(struct fnc_diff_view_state *s)
{
	char		*line = NULL, *st = NULL;
	fsl_size_t	 linelen, idx = 0;
	off_t		 lnoff = 0;
	int		 n, rc = 0;





	if ((n = fsl_fprintf(s->f,"%s %s\n", s->selected_commit->type,
	    s->selected_commit->uuid)) < 0)
		goto end;
	lnoff += n;
	if ((rc = add_line_offset(&s->line_offsets, &s->nlines, lnoff)))
		goto end;

2413
2414
2415
2416
2417
2418
2419







2420
2421
2422
2423

2424


2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463





2464
2465
2466








2467
































2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482














2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
	fputc('\n', s->f);
	++lnoff;
	if ((rc = add_line_offset(&s->line_offsets, &s->nlines, lnoff)))
		goto end;

	st = fsl_strdup(s->selected_commit->comment);
	while ((line = strsep(&st, "\n")) != NULL) {







		if ((n = fsl_fprintf(s->f, "%s\n", line)) < 0)
			goto end;
		lnoff += n;
		if ((rc = add_line_offset(&s->line_offsets, &s->nlines, lnoff)))

			goto end;


	}

	fputc('\n', s->f);
	++lnoff;
	if ((rc = add_line_offset(&s->line_offsets, &s->nlines, lnoff)))
		goto end;

	for (idx = 0; idx < s->selected_commit->changeset.used; ++idx) {
		char				*changeline;
		struct fsl_file_artifact	*file_change;

		file_change = s->selected_commit->changeset.list[idx];

		switch (file_change->change) {
			case FILE_CHANGED:
				changeline = "~  ";
				break;
			case FILE_ADDED:
				changeline = "+  ";
				break;
			case FILE_RENAMED:
				changeline = fsl_mprintf(">  %s -> ",
				   file_change->fc->priorName);
				break;
			case FILE_DELETED:
				changeline = "-  ";
				break;
		}
		if ((n = fsl_fprintf(s->f, "%s%s\n", changeline,
		    file_change->fc->name)) < 0)
			goto end;
		lnoff += n;
		if ((rc = add_line_offset(&s->line_offsets, &s->nlines, lnoff)))
			goto end;
	}

end:
	free(st);
	free(line);





	return rc;
}









static int
































add_line_offset(fpos_t **line_offsets, size_t *nlines, fpos_t off)
{
	fsl_cx	*f = fcli_cx();
	fpos_t	*p;

	p = fsl_realloc(*line_offsets, (*nlines + 1) * sizeof(off));
	if (p == NULL)
		return fsl_cx_err_set(f, FSL_RC_OOM, "fsl_realloc");
	*line_offsets = p;
	(*line_offsets)[*nlines] = off;
	(*nlines)++;

	return 0;
}















static int
diff_commit(fsl_buffer *buf, struct fnc_commit_artifact *commit, int diff_flags,
    bool verbose, int context, int sbs)
{
	fsl_cx			*f = fcli_cx();
	const fsl_card_F	*fc1 = NULL;
	const fsl_card_F	*fc2 = NULL;
	fsl_deck		 d1 = fsl_deck_empty;
	fsl_deck		 d2 = fsl_deck_empty;
	fsl_id_t		 id1, id2;
	fsl_size_t		 i;
	int			 different = 0, rc = 0;

	rc = fsl_sym_to_rid(f, commit->uuid, FSL_SATYPE_CHECKIN, &id2);
	if (rc)
		goto end;
	rc = fsl_deck_load_rid(f, &d2, id2, FSL_SATYPE_CHECKIN);
	if (rc)
		goto end;
	rc = fsl_deck_F_rewind(&d2);
	if (rc)
		goto end;

	/*







>
>
>
>
>
>
>
|
|
|
|
>
|
>
>















|


|


|



|













>
>
>
>
>



>
>
>
>
>
>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|


|

|









>
>
>
>
>
>
>
>
>
>
>
>
>
>


|






|
<


<
<
<
|







2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716

2717
2718



2719
2720
2721
2722
2723
2724
2725
2726
	fputc('\n', s->f);
	++lnoff;
	if ((rc = add_line_offset(&s->line_offsets, &s->nlines, lnoff)))
		goto end;

	st = fsl_strdup(s->selected_commit->comment);
	while ((line = strsep(&st, "\n")) != NULL) {
		linelen = fsl_strlen(line);
		if (linelen >= s->ncols) {
			rc = wrapline(line, s->ncols, s, &lnoff);
			if (rc)
				goto end;
		}
		else {
			if ((n = fsl_fprintf(s->f, "%s\n", line)) < 0)
				goto end;
			lnoff += n;
			if ((rc = add_line_offset(&s->line_offsets, &s->nlines,
			    lnoff)))
				goto end;

		}
	}

	fputc('\n', s->f);
	++lnoff;
	if ((rc = add_line_offset(&s->line_offsets, &s->nlines, lnoff)))
		goto end;

	for (idx = 0; idx < s->selected_commit->changeset.used; ++idx) {
		char				*changeline;
		struct fsl_file_artifact	*file_change;

		file_change = s->selected_commit->changeset.list[idx];

		switch (file_change->change) {
			case FILE_CHANGED:
				changeline = "[~] ";
				break;
			case FILE_ADDED:
				changeline = "[+] ";
				break;
			case FILE_RENAMED:
				changeline = fsl_mprintf("[>] %s -> ",
				   file_change->fc->priorName);
				break;
			case FILE_DELETED:
				changeline = "[-] ";
				break;
		}
		if ((n = fsl_fprintf(s->f, "%s%s\n", changeline,
		    file_change->fc->name)) < 0)
			goto end;
		lnoff += n;
		if ((rc = add_line_offset(&s->line_offsets, &s->nlines, lnoff)))
			goto end;
	}

end:
	free(st);
	free(line);
	if (rc) {
		free(*&s->line_offsets);
		s->line_offsets = NULL;
		s->nlines = 0;
	}
	return rc;
}

/*
 * Wrap long lines at the terminal's available column width. The caller
 * must ensure the ncols_avail parameter has taken into account whether the
 * screen is currently split, and not mistakenly pass in the curses COLS macro
 * without deducting the parent panel's width. This function doesn't break
 * words, and will wrap at the end of the last word that can wholly fit within
 * the ncols_avail limit.
 */
static int
wrapline(char *line, fsl_size_t ncols_avail, struct fnc_diff_view_state *s,
    off_t *lnoff)
{
	char		*word;
	fsl_size_t	 wordlen, cursor = 0;
	int		 n = 0, rc = 0;

	while ((word = strsep(&line, " ")) != NULL) {
		wordlen = fsl_strlen(word);
		if ((cursor + wordlen) >= ncols_avail) {
			fputc('\n', s->f);
			++(*lnoff);
			rc = add_line_offset(&s->line_offsets, &s->nlines,
			    *lnoff);
			if (rc)
				return rc;
			cursor = 0;
		}
		if ((n  = fsl_fprintf(s->f, "%s ", word)) < 0)
			return rc;
		*lnoff += n;
		cursor += n;
	}
	fputc('\n', s->f);
	++(*lnoff);
	if ((rc = add_line_offset(&s->line_offsets, &s->nlines, *lnoff)))
		return rc;

	return 0;
}

static int
add_line_offset(off_t **line_offsets, size_t *nlines, off_t off)
{
	fsl_cx	*f = fcli_cx();
	off_t	*p;

	p = fsl_realloc(*line_offsets, (*nlines + 1) * sizeof(off_t));
	if (p == NULL)
		return fsl_cx_err_set(f, FSL_RC_OOM, "fsl_realloc");
	*line_offsets = p;
	(*line_offsets)[*nlines] = off;
	(*nlines)++;

	return 0;
}

/*
 * Fill the buffer with the differences between commit->uuid and commit->puuid.
 * commit->rid (to load into deck d2) is the *this* version, and commit->puuid
 * (to be loaded into deck d1) is the version we diff against. Step through the
 * deck of F(ile) cards from both versions to determine: (1) if we have new
 * files added (i.e., no F card counterpart in d1); (2) files deleted (i.e., no
 * F card counterpart in d2); (3) or otherwise the same file (i.e., F card
 * exists in both d1 and d2). In cases (1) and (2), we call diff_file_artifact()
 * to dump the complete content of the added/deleted file. In case (3), if the
 * hash (UUID) of each F card is the same AND we're NOT diffing the checkout on
 * disk, there are no changes; however, if we're diffing changes in the local
 * checkout (i.e., commit->rid == 0) against another version, we also need to
 * call diff_file_artifact().
 */
static int
diff_commit(fsl_buffer *buf, struct fnc_commit_artifact *commit, int diff_flags,
    int context, int sbs)
{
	fsl_cx			*f = fcli_cx();
	const fsl_card_F	*fc1 = NULL;
	const fsl_card_F	*fc2 = NULL;
	fsl_deck		 d1 = fsl_deck_empty;
	fsl_deck		 d2 = fsl_deck_empty;
	fsl_id_t		 id1;

	int			 different = 0, rc = 0;




	rc = fsl_deck_load_rid(f, &d2, commit->rid, FSL_SATYPE_CHECKIN);
	if (rc)
		goto end;
	rc = fsl_deck_F_rewind(&d2);
	if (rc)
		goto end;

	/*
2518
2519
2520
2521
2522
2523
2524
2525





2526
2527

2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582




2583
2584


2585
2586

2587


2588
2589
2590
2591
2592
2593
2594
2595



2596
2597
2598


2599
2600

2601
2602
2603
2604
2605
2606
2607



2608

2609
2610
2611
2612
2613
2614
2615
2616

2617
2618
2619
2620
2621
2622
2623
2624
2625
2626









































2627
2628








2629
2630
2631
2632

2633
2634
2635


2636
2637
2638
2639
2640
2641
2642
2643




2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706

2707
2708
2709
2710





2711





2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732





2733





2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
	if (rc)
		goto end;
	rc = fsl_deck_F_rewind(&d1);
	if (rc)
		goto end;

	fsl_deck_F_next(&d1, &fc1);
	for (fsl_deck_F_next(&d2, &fc2); fc2; fsl_deck_F_next(&d2, &fc2)) {





		char const *curr = fc2->priorName ? fc2->priorName : fc2->name;


		/*
		 * Skip lexically smaller filenames present in the parent
		 * commit that are not in this commit as they can't be diffed.
		 */
		different = 0;
		while(fc1 && (0>(different = fsl_strcmp(fc1->name, curr))))
			fsl_deck_F_next(&d1, &fc1);

		if (fc1 && !different) {
			/*
			 * The verbose flag is ignored by fsl_diff_text(), so
			 * we've rolled our own naive verbose diff routine that
			 * dumps the content of newly added or deleted files in
			 * this->commit->changeset to the buffer. This hackery
			 * is used to output these diffs in the correct order:
			 * newly added or deleted files with lexically smaller
			 * names than the modified files are caught here, but we
			 * leave those with lexically larger names to be handled
			 * after the loop finishes processing modified files.
			 */
			if (verbose) {
				diff_flags |= FSL_DIFF_VERBOSE;
				for (i = 0; i < commit->changeset.used;) {
					struct fsl_file_artifact **adf =
					    (struct fsl_file_artifact **)
					    &commit->changeset.list[i++];
					if (*adf && fsl_strcmp((*adf)->fc->name,
					    fc1->name) < 0) {
						verbose_diff(*adf, buf);
						*adf = NULL;
					}
				}
			}
			/*
			 * TODO: The diff algorithm seems to choke on this file.
			 * Presumably because it has really long lines, which
			 * causes it to be classified as a binary file.
			 */
			if (!fsl_strcmp(fc1->name, "skins/bootstrap/css.txt"))
				continue;
			/* Same filename in both commits: modified files. */
			rc = diff_file_artifact(buf, id1, fc1, id2, fc2,
			    diff_flags, verbose, context, sbs);
			if (rc)
				goto end;
			else
				fsl_deck_F_next(&d1, &fc1);
		}
		/*
		 * TODO: This fails on libfossil commit 88a070017ad0cf72 when a
		 * null fc1->name compared with the fc2->name 'fsl_appendf.c'
		 * returns -4, which must still be set after leaving the above
		 * while loop. I think this is because we've consumed the last
		 * fc1 F card, so we break out of the loop, fail the following
		 * if condition, and end up here with different < 0. Maybe




		 * different should be reset to 0 in the above while loop?
		 */


		/* assert(different >= 0 && */
		/*     "The < 0 case was handled by the while loop above!"); */

	}


	/*
	 * Skip lexically larger filenames present in the parent commit that
	 * are not in this commit as they can't be diffed.
	 */
	if (different > 0)
		while (fc1)
			fsl_deck_F_next(&d1, &fc1);
	/*



	 * Output any remaining newly added or deleted files from the changeset
	 * that were missed above due to having lexically larger names.
	 */


	if (verbose)
		fsl_list_visit(&commit->changeset, 0, verbose_diff, buf);


end:
	fsl_deck_finalize(&d1);
	fsl_deck_finalize(&d2);
	return rc;
}




/* TODO: Rename and refactor as this actually diffs technotes too. */

static int
diff_wiki(fsl_buffer *buf, struct fnc_commit_artifact *commit,
    int diff_flags, int context, int sbs)
{
	fsl_cx		*f = fcli_cx();
	fsl_buffer	 wiki = fsl_buffer_empty;
	fsl_buffer	 pwiki = fsl_buffer_empty;
	fsl_id_t	 prid = 0;

	int		 rc = 0;

	fsl_deck *d = NULL;
	d = fsl_deck_malloc();
	if (d == NULL)
		return fsl_cx_err_set(f, FSL_RC_OOM, "fsl_deck_malloc");

	fsl_deck_init(f, d, FSL_SATYPE_ANY);
	if ((rc = fsl_deck_load_rid(f, d, commit->rid, FSL_SATYPE_ANY)))
		goto end;









































	fsl_buffer_append(&wiki, d->W.mem, d->W.used);









	if (commit->puuid == NULL) {
		if (d->P.used > 0)
			commit->puuid = fsl_strdup(d->P.list[0]);
		else

			goto end;
	}



	if ((rc = fsl_sym_to_rid(f, commit->puuid, FSL_SATYPE_ANY, &prid)))
		goto end;
	if ((rc = fsl_deck_load_rid(f, d, prid, FSL_SATYPE_ANY)))
		goto end;
	fsl_buffer_append(&pwiki, d->W.mem, d->W.used);

	rc = fsl_diff_text_to_buffer(&pwiki, &wiki, buf,
                        context, sbs, diff_flags);





end:
	fsl_buffer_clear(&wiki);
	fsl_buffer_clear(&pwiki);
	fsl_deck_finalize(d);
	return rc;
}

/*
 * As a workaround for the inoperative FSL_DIFF_VERBOSE flag, parse the
 * changeset for added or deleted files and dump the contents to the buffer
 * with the plus '+' or minus '-' prefix, respectively.
 */
static int
verbose_diff(void *ffa, void *state)
{
	struct fsl_file_artifact	*fa = ffa;
	fsl_buffer			*b = state;
	fsl_buffer			 c = fsl_buffer_empty;
	fsl_cx				*f = fcli_cx();
	char				*line, *st = NULL;

	if (fa->change == FILE_CHANGED || fa->change == FILE_RENAMED)
		return 0;

	fsl_buffer_appendf(b, "\nIndex: %s\n%.71c\n", fa->fc->name, '=');
	fsl_buffer_appendf(b, "hash - %s\nhash + %s\n", fa->change ==
	    FILE_ADDED  ? "/dev/null" : fa->fc->uuid, fa->change ==
	    FILE_DELETED ? "/dev/null" : fa->fc->uuid);
	fsl_buffer_appendf(b, "--- %s\n+++ %s\n\n", fa->change == FILE_ADDED ?
	    "/dev/null" : fa->fc->name, fa->change == FILE_DELETED ?
	    "/dev/null" : fa->fc->name);

	fsl_card_F_content(f, fa->fc, &c);
	st = fsl_strdup(fsl_buffer_str(&c));
	while ((line = strsep(&st, "\n")) != NULL)
		fsl_buffer_appendf(b, "%c%s\n", fa->change == FILE_ADDED ?
		    '+' : '-', line);

	return 0;
}

static int
diff_file_artifact(fsl_buffer *buf, fsl_id_t vid1, fsl_card_F const *fc1,
    fsl_id_t vid2, fsl_card_F const *fc2, int diff_flags, bool verbose,
    int context, int sbs)
{
	const fsl_card_F	*hashchk = NULL;
	fsl_cx			*f = fcli_cx();
	fsl_buffer		 fbuf1 = fsl_buffer_empty;
	fsl_buffer		 fbuf2 = fsl_buffer_empty;
	fsl_buffer		 fhash = fsl_buffer_empty;
	fsl_time_t		 rmtime = 0;
	fsl_time_t		 fmtime = 0;
	int			 rc = 0;

	if (vid1 > 0 && vid2 > 0 && !fsl_uuidcmp(fc1->uuid, fc2->uuid))
		return 0;

	assert(vid1 != vid2);

	fhash.used = fbuf2.used = fbuf1.used = 0;


	if (vid1 == 0) {
		assert(vid2 != 0);
		rc = fsl_ckout_file_content(f, fc1->name, &fbuf1);
		if (!rc) {





			rc = fsl_sha1sum_buffer(&fbuf1, &fhash);





			if (!rc) {
			hashchk = fc2;
			rc = fsl_ckout_mtime(f, vid1, fc1, NULL,
			    &fmtime);
			}
		}
	} else {
		rc = fsl_card_F_content(f, fc1, &fbuf1);
		if (!rc && (0 == vid2))
			/* Collect repo-side mtime if the other version == 0. */
			rc = fsl_ckout_mtime(f, vid1, fc1, &rmtime, NULL);
	}

	if (rc)
		goto end;

	/* Repeat for vid2. */
	if (vid2 == 0) {
		assert(vid1 != 0);
		rc = fsl_ckout_file_content(f, fc2->name, &fbuf2);
		if (!rc) {





			rc = fsl_sha1sum_buffer(&fbuf2, &fhash);





			if (!rc) {
			hashchk = fc1;
			rc = fsl_ckout_mtime(f, vid2, fc2, NULL,
			    &fmtime);
			}
		}
	} else {
		rc = fsl_card_F_content(f, fc2, &fbuf2);
		if(!rc && (vid1 == 0)){
			/* Collect repo-side mtime if the other version == 0. */
			rc = fsl_ckout_mtime(f, vid2, fc2, &rmtime, NULL);
		}
	}








|
>
>
>
>
>
|
|
>
|
<
<
<
|
<
<
|
|
<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
|
<
<
<
<
<
<
<
|
<
<
<
|
<
<
<
|
|
<
|
<
<
<
<
<
<
<
|
>
>
>
>
|
<
>
>
|
<
>
|
>
>
|
<
|
<
|
|
|
<
>
>
>
|
<
<
>
>
|
<
>
|






>
>
>
|
>

|






>










>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
>
>
>
>
>
>
>
>



|
>

|
|
>
>






|
|
>
>
>
>








<
<
<
<
<

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

|
<









|
<
<





>
|



>
>
>
>
>
|
>
>
>
>
>

|
|
|


|

|







|
|



>
>
>
>
>
|
>
>
>
>
>

|
|
|


|







2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754



2755


2756
2757











2758






2759







2760



2761



2762
2763

2764







2765
2766
2767
2768
2769
2770

2771
2772
2773

2774
2775
2776
2777
2778

2779

2780
2781
2782

2783
2784
2785
2786


2787
2788
2789

2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902





2903





























2904
2905

2906
2907
2908
2909
2910
2911
2912
2913
2914
2915


2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
	if (rc)
		goto end;
	rc = fsl_deck_F_rewind(&d1);
	if (rc)
		goto end;

	fsl_deck_F_next(&d1, &fc1);
	fsl_deck_F_next(&d2, &fc2);
	while (fc1 || fc2) {
		const fsl_card_F	*a = NULL, *b = NULL;
		const char		*curr;

		if (fc2)
			curr = fc2->priorName ? fc2->priorName : fc2->name;
		else if (fc1)
			curr = fc1->priorName ? fc1->priorName : fc1->name;
		if (!fc1)	/* File added. */



			different = 1;


		else if (!fc2)	/* File deleted. */
			different = -1;











		else		/* Same filename in both versions. */






			different = fsl_strcmp(fc1->name, curr);











		if (different) {



			if (different > 0) {
				b = fc2;

				fsl_deck_F_next(&d2, &fc2);







			} else if (different < 0) {
				a = fc1;
				fsl_deck_F_next(&d1, &fc1);
			}
			rc = diff_file_artifact(buf, id1, a, commit->rid, b,
			    diff_flags, context, sbs);

		} else if (!fsl_uuidcmp(fc1->uuid, fc2->uuid)) {
			if (!id1 || !commit->rid) {  /* Diff checkout. */
				rc = diff_file_artifact(buf, id1, fc1,

				    commit->rid, fc2, diff_flags, context, sbs);
			}
			fsl_deck_F_next(&d1, &fc1);
			fsl_deck_F_next(&d2, &fc2);
		} else {  /* File changed. */

			rc = diff_file_artifact(buf, id1, fc1, commit->rid, fc2,

			    diff_flags, context, sbs);
			fsl_deck_F_next(&d1, &fc1);
			fsl_deck_F_next(&d2, &fc2);

		}
		if (rc == FSL_RC_RANGE || rc == FSL_RC_TYPE) {
			fsl_buffer_append(buf,
			    "\nBinary files cannot be diffed\n", -1);


			rc = 0;
			fsl_cx_err_reset(f);
		} else if (rc)

			goto end;
	}
end:
	fsl_deck_finalize(&d1);
	fsl_deck_finalize(&d2);
	return rc;
}

/*
 * Parse the deck of non-checkin commits to present a 'fossil ui' equivalent
 * of the corresponding artifact when selected from the timeline.
 * TODO: Rename this horrible function name.
 */
static int
diff_non_checkin(fsl_buffer *buf, struct fnc_commit_artifact *commit,
    int diff_flags, int context, int sbs)
{
	fsl_cx		*f = fcli_cx();
	fsl_buffer	 wiki = fsl_buffer_empty;
	fsl_buffer	 pwiki = fsl_buffer_empty;
	fsl_id_t	 prid = 0;
	fsl_size_t	 idx;
	int		 rc = 0;

	fsl_deck *d = NULL;
	d = fsl_deck_malloc();
	if (d == NULL)
		return fsl_cx_err_set(f, FSL_RC_OOM, "fsl_deck_malloc");

	fsl_deck_init(f, d, FSL_SATYPE_ANY);
	if ((rc = fsl_deck_load_rid(f, d, commit->rid, FSL_SATYPE_ANY)))
		goto end;

	/*
	 * Present ticket commits as a series of field: value tuples as per
	 * the Fossil UI /info/UUID view.
	 */
	if (d->type == FSL_SATYPE_TICKET) {
		for (idx = 0; idx < d->J.used; ++idx) {
			fsl_card_J *ticket = d->J.list[idx];
			bool icom = !fsl_strncmp(ticket->field, "icom", 4);
			fsl_buffer_appendf(buf, "%d. %s:%s%s%c\n", idx + 1,
			    ticket->field, icom ? "\n\n" : " ", ticket->value,
			    icom ? '\n' : ' ');
		}
		goto end;
	}

	if (d->type == FSL_SATYPE_CONTROL) {
		for (idx = 0; idx < d->T.used; ++idx) {
			fsl_card_T *ctl = d->T.list[idx];
			fsl_buffer_appendf(buf, "Tag %d ", idx + 1);
			switch (ctl->type) {
			case FSL_TAGTYPE_CANCEL:
				fsl_buffer_append(buf, "[CANCEL]", -1);
				break;
			case FSL_TAGTYPE_ADD:
				fsl_buffer_append(buf, "[ADD]", -1);
				break;
			case FSL_TAGTYPE_PROPAGATING:
				fsl_buffer_append(buf, "[PROPAGATE]", -1);
				break;
			default:
				break;
			}
			if (ctl->uuid)
				fsl_buffer_appendf(buf, "\ncheckin %s",
				    ctl->uuid);
			fsl_buffer_appendf(buf, "\n%s", ctl->name);
			if (!fsl_strcmp(ctl->name, "branch"))
				commit->branch = fsl_strdup(ctl->value);
			if (ctl->value)
				fsl_buffer_appendf(buf, " -> %s", ctl->value);
			fsl_buffer_append(buf, "\n\n", 2);
		}
		goto end;
	}
	/*
	 * If neither a ticket nor control artifact, we assume it's a wiki, so
	 * check if it has a parent commit to diff against. If not, append the
	 * entire wiki card content.
	 */
	fsl_buffer_append(&wiki, d->W.mem, d->W.used);
	if (commit->puuid == NULL) {
		if (d->P.used > 0)
			commit->puuid = fsl_strdup(d->P.list[0]);
		else {
			fsl_buffer_copy(&wiki, buf);
			goto end;
		}
	}

	/* Diff the artifacts if a parent is found. */
	if ((rc = fsl_sym_to_rid(f, commit->puuid, FSL_SATYPE_ANY, &prid)))
		goto end;
	if ((rc = fsl_deck_load_rid(f, d, prid, FSL_SATYPE_ANY)))
		goto end;
	fsl_buffer_append(&pwiki, d->W.mem, d->W.used);

	rc = fsl_diff_text_to_buffer(&pwiki, &wiki, buf, context, sbs,
	    diff_flags);

	/* If a technote, provide the full content after its diff. */
	if (d->type == FSL_SATYPE_TECHNOTE)
		fsl_buffer_appendf(buf, "\n---\n\n%s", wiki.mem);

end:
	fsl_buffer_clear(&wiki);
	fsl_buffer_clear(&pwiki);
	fsl_deck_finalize(d);
	return rc;
}






static int





























diff_file_artifact(fsl_buffer *buf, fsl_id_t vid1, fsl_card_F const *fc1,
    fsl_id_t vid2, fsl_card_F const *fc2, int diff_flags, int context, int sbs)

{
	const fsl_card_F	*hashchk = NULL;
	fsl_cx			*f = fcli_cx();
	fsl_buffer		 fbuf1 = fsl_buffer_empty;
	fsl_buffer		 fbuf2 = fsl_buffer_empty;
	fsl_buffer		 fhash = fsl_buffer_empty;
	fsl_time_t		 rmtime = 0;
	fsl_time_t		 fmtime = 0;
	int			 rc = 0;
	bool			 verbose;



	assert(vid1 != vid2);

	fhash.used = fbuf2.used = fbuf1.used = 0;

	/* If diffing against local checkout, hash file on disk to compare. */
	if (vid1 == 0 && fc1) {
		assert(vid2 != 0);
		rc = fsl_ckout_file_content(f, fc1->name, &fbuf1);
		if (!rc) {
			switch (fsl_strlen(fc1->uuid)) {
			case FSL_STRLEN_K256:
				rc = fsl_sha3sum_buffer(&fbuf1, &fhash);
				break;
			case FSL_STRLEN_SHA1:
				rc = fsl_sha1sum_buffer(&fbuf1, &fhash);
				break;
			default:
				return fcli_err_set(FSL_RC_SIZE_MISMATCH,
				    "invalid artifact uuid [%s], fc1->uuid");
			}
			if (!rc) {
				hashchk = fc2;
				rc = fsl_ckout_mtime(f, vid1, fc1, NULL,
				    &fmtime);
			}
		}
	} else if (fc1) {
		rc = fsl_card_F_content(f, fc1, &fbuf1);
		if (!rc && (vid2 == 0))
			/* Collect repo-side mtime if the other version == 0. */
			rc = fsl_ckout_mtime(f, vid1, fc1, &rmtime, NULL);
	}

	if (rc)
		goto end;

	/* If diffing the local checkout, hash the file on disk to compare. */
	if (vid2 == 0 && fc2) {
		assert(vid1 != 0);
		rc = fsl_ckout_file_content(f, fc2->name, &fbuf2);
		if (!rc) {
			switch (fsl_strlen(fc2->uuid)) {
			case FSL_STRLEN_K256:
				rc = fsl_sha3sum_buffer(&fbuf2, &fhash);
				break;
			case FSL_STRLEN_SHA1:
				rc = fsl_sha1sum_buffer(&fbuf2, &fhash);
				break;
			default:
				return fcli_err_set(FSL_RC_SIZE_MISMATCH,
				    "invalid artifact uuid [%s], fc2->uuid");
			}
			if (!rc) {
				hashchk = fc1;
				rc = fsl_ckout_mtime(f, vid2, fc2, NULL,
				    &fmtime);
			}
		}
	} else if (fc2) {
		rc = fsl_card_F_content(f, fc2, &fbuf2);
		if(!rc && (vid1 == 0)){
			/* Collect repo-side mtime if the other version == 0. */
			rc = fsl_ckout_mtime(f, vid2, fc2, &rmtime, NULL);
		}
	}

2758
2759
2760
2761
2762
2763
2764

2765
2766
2767
2768

2769

2770
2771
2772
2773

2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
		 * One of the above is a local file and rmtime holds the
		 * repo-side mtime of the other. Assume naively that same time
		 * indicates the same content, which'll be the case more often
		 * than not.
		 */
		goto end;
	} else {

	fsl_buffer_appendf(buf, "\nIndex: %s\n%.71c\n",
	    fc2->name, '=');
		fsl_buffer_appendf(buf, "hash - %s\nhash + %s\n",
		    fc1->uuid, fc2->uuid);

		fsl_buffer_appendf(buf, "--- %s\n+++ %s\n", fc1->name,

		    fc2->name);
		/* rc = fsl_diff_text(fbuf1, fbuf2, fsl_output_f_buffer, */
		/*     (void *)buf, s->context, VDiffApp.sbsWidth, */
		/*     s->diff_flags); */

		rc = fsl_diff_text_to_buffer(&fbuf1, &fbuf2, buf,
		    context, sbs, diff_flags);
		if (rc)
		fcli_err_set(rc, "Error %s: generating diff %s %s",
		    fsl_rc_cstr(rc), fc1->name ? fc1->name :
		    "/dev/null", fc2->name ? fc2->name : "/dev/null");
		else
		f_out("\n");
	}
end:
	fsl_buffer_clear(&fbuf1);
	fsl_buffer_clear(&fbuf2);
	return rc;
}








>
|
|

|
>
|
>
|
<
<
<
>
|
|

|
|
|
<
<







2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008



3009
3010
3011
3012
3013
3014
3015


3016
3017
3018
3019
3020
3021
3022
		 * One of the above is a local file and rmtime holds the
		 * repo-side mtime of the other. Assume naively that same time
		 * indicates the same content, which'll be the case more often
		 * than not.
		 */
		goto end;
	} else {
		verbose = (diff_flags & FSL_DIFF_VERBOSE) != 0 ? true : false;
		fsl_buffer_appendf(buf, "\nIndex: %s\n%.71c\n",
		    fc2 ? fc2->name : fc1->name, '=');
		fsl_buffer_appendf(buf, "hash - %s\nhash + %s\n",
		    fc1 ? fc1->uuid : "/dev/null",
		    fc2 ? fc2->uuid : "/dev/null");
		fsl_buffer_appendf(buf, "--- %s\n+++ %s\n",
		    fc1 ? fc1->name : "/dev/null",
		    fc2 ? fc2->name : "/dev/null");



		if (verbose || (fc1 && fc2))
			rc = fsl_diff_text_to_buffer(&fbuf1, &fbuf2, buf,
			    context, sbs, diff_flags);
		if (rc)
			fcli_err_set(rc, "Error %s: generating diff %s %s",
			    fsl_rc_cstr(rc), fc1 ? fc1->name : "/dev/null",
			    fc2 ? fc2->name : "/dev/null");


	}
end:
	fsl_buffer_clear(&fbuf1);
	fsl_buffer_clear(&fbuf2);
	return rc;
}

2803
2804
2805
2806
2807
2808
2809
2810

2811
2812
2813
2814
2815
2816
2817
	if (!rc) {
		assert(fname.used);
		if (fname.mem[fname.used - 1] == '/') {
			rc = fsl_cx_err_set(f, FSL_RC_MISUSE,
			    "Filename may not have a trailing slash.");
		} else {
			dest->used = 0;
			rc = fsl_buffer_fill_from_filename(dest, fsl_buffer_cstr(&fname));

		}
	}
	fsl_buffer_clear(&fname);

	return rc;
}








|
>







3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
	if (!rc) {
		assert(fname.used);
		if (fname.mem[fname.used - 1] == '/') {
			rc = fsl_cx_err_set(f, FSL_RC_MISUSE,
			    "Filename may not have a trailing slash.");
		} else {
			dest->used = 0;
			rc = fsl_buffer_fill_from_filename(dest,
			    fsl_buffer_cstr(&fname));
		}
	}
	fsl_buffer_clear(&fname);

	return rc;
}

2865
2866
2867
2868
2869
2870
2871

2872
2873
2874
2875
2876
2877
2878
2879
2880

2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892

static int
write_diff(struct fnc_view *view, const char *headln)
{
	struct fnc_diff_view_state	*s = &view->state.diff;
	fsl_cx				*f = fcli_cx();
	regmatch_t			*regmatch = &view->regmatch;

	wchar_t				*wcstr;
	char				*line;
	size_t				 linesz = 0;
	ssize_t				 linelen;
	fpos_t				 line_offset;
	int				 wstrlen;
	int				 max_lines = view->nlines;
	int				 nlines = s->nlines;
	int				 rc = 0, nprintln = 0;


	line_offset = s->line_offsets[s->first_line_onscreen - 1];
	if (fsetpos(s->f, &line_offset))
		return fsl_cx_err_set(f, fsl_errno_to_rc(errno, FSL_RC_ERROR),
		    "fsetpos");

	/*
	 * werase() fails to properly clear the parent screen when viewing
	 * a diff and scrolling through commits with {J|,}. The headln of
	 * <commit-type> <uuid> is redrawn on consecutive lines.
	 */
	/* werase(view->window); */







>




|




>


|

|







3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129

static int
write_diff(struct fnc_view *view, const char *headln)
{
	struct fnc_diff_view_state	*s = &view->state.diff;
	fsl_cx				*f = fcli_cx();
	regmatch_t			*regmatch = &view->regmatch;
	struct fnc_colour		*c = NULL;
	wchar_t				*wcstr;
	char				*line;
	size_t				 linesz = 0;
	ssize_t				 linelen;
	off_t				 line_offset;
	int				 wstrlen;
	int				 max_lines = view->nlines;
	int				 nlines = s->nlines;
	int				 rc = 0, nprintln = 0;
	int				 match = -1;

	line_offset = s->line_offsets[s->first_line_onscreen - 1];
	if (fseeko(s->f, line_offset, SEEK_SET))
		return fsl_cx_err_set(f, fsl_errno_to_rc(errno, FSL_RC_ERROR),
		    "fseeko");

	/*
	 * werase() fails to properly clear the parent screen when viewing
	 * a diff and scrolling through commits with {J|,}. The headln of
	 * <commit-type> <uuid> is redrawn on consecutive lines.
	 */
	/* werase(view->window); */
2925
2926
2927
2928
2929
2930
2931





2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943




2944
2945
2946
2947
2948
2949
2950
				break;
			}
			fsl_cx_err_set(f, fsl_errno_to_rc(ferror(s->f),
			    FSL_RC_IO), "getline");
			goto end;
		}






		if (s->first_line_onscreen + nprintln == s->matched_line &&
		    regmatch->rm_so >= 0 && regmatch->rm_so < regmatch->rm_eo) {
			rc = write_matched_line(&wstrlen, line, view->ncols, 0,
			    view->window, regmatch);
			if (rc)
				goto end;
		} else {
			rc = formatln(&wcstr, &wstrlen, line, view->ncols, 0);
			if (rc)
				goto end;
			waddwstr(view->window, wcstr);
		}




		if (wstrlen <= view->ncols - 1)
			waddch(view->window, '\n');
		++nprintln;
	}
	if (nprintln >= 1)
		s->last_line_onscreen = s->first_line_onscreen + (nprintln - 1);
	else







>
>
>
>
>












>
>
>
>







3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
				break;
			}
			fsl_cx_err_set(f, fsl_errno_to_rc(ferror(s->f),
			    FSL_RC_IO), "getline");
			goto end;
		}

		if (s->colour && (match = fsl_list_index_of(&s->colours, line,
		    match_line)) != -1)
			c = s->colours.list[match];
		if (c)
			wattr_on(view->window, COLOR_PAIR(c->scheme), NULL);
		if (s->first_line_onscreen + nprintln == s->matched_line &&
		    regmatch->rm_so >= 0 && regmatch->rm_so < regmatch->rm_eo) {
			rc = write_matched_line(&wstrlen, line, view->ncols, 0,
			    view->window, regmatch);
			if (rc)
				goto end;
		} else {
			rc = formatln(&wcstr, &wstrlen, line, view->ncols, 0);
			if (rc)
				goto end;
			waddwstr(view->window, wcstr);
		}
		if (c) {
			wattr_off(view->window, COLOR_PAIR(c->scheme), NULL);
			c = NULL;
		}
		if (wstrlen <= view->ncols - 1)
			waddch(view->window, '\n');
		++nprintln;
	}
	if (nprintln >= 1)
		s->last_line_onscreen = s->first_line_onscreen + (nprintln - 1);
	else
2963
2964
2965
2966
2967
2968
2969









2970
2971
2972
2973
2974
2975
2976
		wstandend(view->window);
	}
end:
	free(wcstr);
	free(line);
	return rc;
}










static bool
screen_is_shared(struct fnc_view *view)
{
	if (view_is_parent(view)) {
		if (view->child == NULL || view->child->active ||
		    !screen_is_split(view->child))







>
>
>
>
>
>
>
>
>







3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
		wstandend(view->window);
	}
end:
	free(wcstr);
	free(line);
	return rc;
}

static int
match_line(const void *ln, const void *key)
{
	struct fnc_colour *c = (struct fnc_colour *)key;
	const char *line = ln;

	return regexec(&c->regex, line, 0, NULL, 0);
}

static bool
screen_is_shared(struct fnc_view *view)
{
	if (view_is_parent(view)) {
		if (view->child == NULL || view->child->active ||
		    !screen_is_split(view->child))
3070
3071
3072
3073
3074
3075
3076
3077



3078
3079
3080
3081
3082
3083
3084

	panel = panel_above(view->panel);
	if (panel == NULL)
		return;

	view_above = panel_userptr(panel);
	mvwvline(view->window, view->start_ln, view_above->start_col - 1,
	(strcmp(codeset, "UTF-8") == 0) ? ACS_VLINE : '|', view->nlines);



}

static int
diff_input_handler(struct fnc_view **new_view, struct fnc_view *view, int ch)
{
	struct fnc_diff_view_state	*s = &view->state.diff;
	struct fnc_tl_view_state	*tlstate;







|
>
>
>







3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342

	panel = panel_above(view->panel);
	if (panel == NULL)
		return;

	view_above = panel_userptr(panel);
	mvwvline(view->window, view->start_ln, view_above->start_col - 1,
	    (strcmp(codeset, "UTF-8") == 0) ? ACS_VLINE : '|', view->nlines);
#ifdef __linux__
	wnoutrefresh(view->window);
#endif
}

static int
diff_input_handler(struct fnc_view **new_view, struct fnc_view *view, int ch)
{
	struct fnc_diff_view_state	*s = &view->state.diff;
	struct fnc_tl_view_state	*tlstate;
3124
3125
3126
3127
3128
3129
3130

3131
3132
3133


3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
	case CTRL('b'):
		if (s->first_line_onscreen == 1)
			break;
		i = 0;
		while (i++ < view->nlines - 1 && s->first_line_onscreen > 1)
			--s->first_line_onscreen;
		break;

	case 'i':
	case 'v':
	case 'w':


		if (ch == 'i')
			s->diff_flags ^= FSL_DIFF_INVERT;
		if (ch == 'v')
			s->verbose = s->verbose == false;
		if (ch == 'w')
			s->diff_flags ^= FSL_DIFF_IGNORE_ALLWS;
		wclear(view->window);
		s->first_line_onscreen = 1;
		s->last_line_onscreen = view->nlines;
		show_diff_status(view);
		rc = create_diff(s);







>



>
>



|







3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
	case CTRL('b'):
		if (s->first_line_onscreen == 1)
			break;
		i = 0;
		while (i++ < view->nlines - 1 && s->first_line_onscreen > 1)
			--s->first_line_onscreen;
		break;
	case 'c':
	case 'i':
	case 'v':
	case 'w':
		if (ch == 'c')
			s->colour = s->colour == false;
		if (ch == 'i')
			s->diff_flags ^= FSL_DIFF_INVERT;
		if (ch == 'v')
			s->diff_flags ^= FSL_DIFF_VERBOSE;
		if (ch == 'w')
			s->diff_flags ^= FSL_DIFF_IGNORE_ALLWS;
		wclear(view->window);
		s->first_line_onscreen = 1;
		s->last_line_onscreen = view->nlines;
		show_diff_status(view);
		rc = create_diff(s);
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
		if (view->searching == SEARCH_FORWARD)
			start_ln = 1;
		else
			start_ln = s->nlines;
	}

	while (1) {
		fpos_t offset;

		if (start_ln <= 0 || start_ln > (int)s->nlines) {
			if (s->matched_line == 0) {
				view->search_status = SEARCH_CONTINUE;
				break;
			}








|







3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
		if (view->searching == SEARCH_FORWARD)
			start_ln = 1;
		else
			start_ln = s->nlines;
	}

	while (1) {
		off_t offset;

		if (start_ln <= 0 || start_ln > (int)s->nlines) {
			if (s->matched_line == 0) {
				view->search_status = SEARCH_CONTINUE;
				break;
			}

3289
3290
3291
3292
3293
3294
3295

3296
3297
3298
3299
3300
3301
3302

3303
3304
3305
3306
3307
3308
3309
	return 0;
}

static int
close_diff_view(struct fnc_view *view)
{
	struct fnc_diff_view_state	*s = &view->state.diff;

	fsl_cx				*f = fcli_cx();
	int				 rc = 0;

	if (s->f && fclose(s->f) == EOF)
		rc = fsl_cx_err_set(f, fsl_errno_to_rc(errno, FSL_RC_IO),
		    "fclose");
	free(s->line_offsets);

	s->line_offsets = NULL;
	s->nlines = 0;
	return rc;
}

static void
fnc_resizeterm(void)







>







>







3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
	return 0;
}

static int
close_diff_view(struct fnc_view *view)
{
	struct fnc_diff_view_state	*s = &view->state.diff;
	struct fsl_list_state		st = { FNC_COLOUR_OBJ };
	fsl_cx				*f = fcli_cx();
	int				 rc = 0;

	if (s->f && fclose(s->f) == EOF)
		rc = fsl_cx_err_set(f, fsl_errno_to_rc(errno, FSL_RC_IO),
		    "fclose");
	free(s->line_offsets);
	fsl_list_clear(&s->colours, fsl_list_object_free, &st);
	s->line_offsets = NULL;
	s->nlines = 0;
	return rc;
}

static void
fnc_resizeterm(void)
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408

3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452

3453
3454
3455
3456
3457
3458
3459
3460



3461









3462

3463
3464



3465





3466








3467
3468
3469
3470

3471
3472
3473



3474
3475






3476
3477



3478


3479
3480
3481
3482
3483




















3484
3485


3486
3487
3488
3489
3490
3491
3492
3493
3494




3495



3496




3497
3498
3499


3500















3501
3502
3503















3504

3505

3506




3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
		rec_sigwinch = 1;
	}
}

static void
sigpipe_handler(int sig)
{
	rec_sigpipe = 1;
#if !defined(__OpenBSD__) || !defined (__APPLE__)
	struct sigaction	sact;
	int			e;


	memset(&sact, 0, sizeof(sact));
	sact.sa_handler = SIG_IGN;
	sact.sa_flags = SA_RESTART;
	e = sigaction(SIGPIPE, &sact, NULL);
	if (e)
		err(1, "SIGPIPE");
#else
#if defined(__OpenBSD__) || defined(__APPLE__)
	int	sock = socket(AF_UNIX, SOCK_STREAM, 0);
	int	on = 1;
#ifdef __OpenBSD__
	setsockopt(sock, SOL_SOCKET, MSG_NOSIGNAL, (void *)&on, sizeof(int));
#endif /* OpenBSD */
#ifdef __APPLE__
	setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, (void *)&on, sizeof(int));
#endif /* Apple */
#endif /* OpenBSD or Apple */
#endif /* not OpenBSD or Apple */
}

static void
sigcont_handler(int sig)
{
	rec_sigcont = 1;
}

__dead void
usage(void)
{
	/*
	 * It looks like the fsl_cx f member of the ::fcli singleton has
	 * already been cleaned up by the time this wrapper is called from
	 * fcli_help() after hijacking the process whenever the '--help'
	 * argument is passsed on the command line, so we can't use the
	 * f->output fsl_outputer implementation as we would like.
	 */
	/* fsl_cx *f = fcli_cx(); */
	/* f->output = fsl_outputer_FILE; */
	/* f->output.state.state = (fnc_init.err == true) ? stderr : stdout; */
        FILE *f = (fnc_init.err == true) ? stderr : stdout;
	size_t idx = 0;

	endwin();


	if (fcli.argc)
		for (idx = 0; idx < nitems(fnc_init.cmd_args); ++idx) {
			if (!fsl_strcmp(fcli.argv[0],
			    fnc_init.cmd_args[idx].name)) {
				fsl_fprintf(f, "[%s] command:\n\n usage:%s\n\n",
				    fnc_init.cmd_args[idx].name,
				    fnc_init.fnc_usage_cb[idx]());
				fcli_cliflag_help(fnc_init.cmd_args[idx].flags);



				exit(fnc_init.err);









			}

		}




	if (fnc_init.err)





		fcli_err_report(true);









	fcli_command_help(fnc_init.cmd_args, false);
	fsl_fprintf(f, "[usage]\n\n%s\n\n%s\n\n%s\n\n  note: %s "
	    "with no args defaults to the timeline command.\n\n",

	    usage_timeline(), usage_diff(), usage_blame(), getprogname());

	exit(fnc_init.err);



}







const char *
usage_timeline(void)



{


	return fsl_mprintf(" %s timeline [-T tag] [-b branch] [-c hash]"
	    " [-h|--help] [-n n] [-t type] [-u user] [-z|--utc]\n"
	    "  e.g.: %s timeline --type ci -u jimmy ",
	    getprogname(), getprogname());
}





















const char *


usage_diff(void)
{
	return fsl_mprintf(" %s diff [-h|--help] [-i|--invert] "
	    "[-w|--whitespace] [-x|--context lines] artifact1 artifact2\n"
	    "  e.g.: %s diff --context 3 d34db33f c0ff33",
	    getprogname(), getprogname());
}

const char *




usage_blame(void)



{




	return fsl_mprintf(" %s blame [-h|--help] [-c hash] artifact\n"
	    "  e.g.: %s blame -c d34db33f src/foo.c" ,
	    getprogname(), getprogname());


}
















static int
cmd_diff()















{

	/* Not yet implemened. */

	f_out("%s diff can not yet be called from the command line;\naccess "




	"the diff view by selecting a commit from within the timeline.\n",
	getprogname());
	return FSL_RC_NYI;
}

static int
cmd_blame()
{
	/* Not yet implemened. */
	f_out("%s blame is not yet implemented.\n", getprogname());
	return FSL_RC_NYI;
}

static void
fnc_show_version(void)
{
	printf("%s %s\n", getprogname(), PRINT_VERSION);
}








<
<



>






<
<
<
<
<
<
<
<
<
<
<
<








|












|




>
|

|

|
|
|

>
>
>
|
>
>
>
>
>
>
>
>
>
|
>
|

>
>
>
|
>
>
>
>
>
|
>
>
>
>
>
>
>
>
|
|
<
<
>
|
|
|
>
>
>


>
>
>
>
>
>
|
<
>
>
>
|
>
>
|
<
<
<
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
<
>
>
|
<
|
<
<
<
|

<
>
>
>
>
|
>
>
>
|
>
>
>
>
|
<
<
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
|
>
|
>
>
>
>
|
|
|



|


|






|


3660
3661
3662
3663
3664
3665
3666


3667
3668
3669
3670
3671
3672
3673
3674
3675
3676












3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748


3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764

3765
3766
3767
3768
3769
3770
3771



3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793

3794
3795
3796

3797



3798
3799

3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813


3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
		rec_sigwinch = 1;
	}
}

static void
sigpipe_handler(int sig)
{


	struct sigaction	sact;
	int			e;

	rec_sigpipe = 1;
	memset(&sact, 0, sizeof(sact));
	sact.sa_handler = SIG_IGN;
	sact.sa_flags = SA_RESTART;
	e = sigaction(SIGPIPE, &sact, NULL);
	if (e)
		err(1, "SIGPIPE");












}

static void
sigcont_handler(int sig)
{
	rec_sigcont = 1;
}

__dead static void
usage(void)
{
	/*
	 * It looks like the fsl_cx f member of the ::fcli singleton has
	 * already been cleaned up by the time this wrapper is called from
	 * fcli_help() after hijacking the process whenever the '--help'
	 * argument is passsed on the command line, so we can't use the
	 * f->output fsl_outputer implementation as we would like.
	 */
	/* fsl_cx *f = fcli_cx(); */
	/* f->output = fsl_outputer_FILE; */
	/* f->output.state.state = (fnc_init.err == true) ? stderr : stdout; */
        FILE *f = fnc_init.err ? stderr : stdout;
	size_t idx = 0;

	endwin();

	/* If a command was passed on the CLI, output its corresponding help. */
	if (fnc_init.cmdarg)
		for (idx = 0; idx < nitems(fnc_init.cmd_args); ++idx) {
			if (!fsl_strcmp(fnc_init.cmdarg,
			    fnc_init.cmd_args[idx].name)) {
				fsl_fprintf(f, "[%s] command:\n\n usage:",
				    fnc_init.cmd_args[idx].name);
				    fnc_init.fnc_usage_cb[idx]();
				fcli_cliflag_help(fnc_init.cmd_args[idx].flags);
				exit(fcli_end_of_main(fnc_init.err));
			}
		}

	/* Otherwise, output help/usage for all commands. */
	fcli_command_help(fnc_init.cmd_args, false);
	fsl_fprintf(f, "[usage]\n\n");
	usage_timeline();
	usage_diff();
	usage_blame();
	fsl_fprintf(f, "  note: %s "
	    "with no args defaults to the timeline command.\n\n",
	    fcli_progname());

	exit(fcli_end_of_main(fnc_init.err));
}

static void
usage_timeline(void)
{
	fsl_fprintf(fnc_init.err ? stderr : stdout,
	    " %s timeline [-T tag] [-b branch] [-c hash]"
	    " [-h|--help] [-n n] [-t type] [-u user] [-z|--utc]\n"
	    "  e.g.: %s timeline --type ci -u jimmy\n\n",
	    fcli_progname(), fcli_progname());
}

static void
usage_diff(void)
{
	fsl_fprintf(fnc_init.err ? stderr : stdout,
	    " %s diff [-c|--no-colour] [-h|--help] [-i|--invert]"
	    " [-q|--quiet] [-w|--whitespace] [-x|--context n] "
	    "[hash ...]\n  e.g.: %s diff --context 3 d34db33f c0ff33\n\n",
	    fcli_progname(), fcli_progname());
}



static void
usage_blame(void)
{
	fsl_fprintf(fnc_init.err ? stderr : stdout,
	    " %s blame [-h|--help] [-c hash] artifact\n"
	    "  e.g.: %s blame -c d34db33f src/foo.c\n\n" ,
	    fcli_progname(), fcli_progname());
}

static int
cmd_diff(fcli_command const *argv)
{
	fsl_cx				*f = fcli_cx();
	struct fnc_view			*view;
	struct fnc_commit_artifact	*commit = NULL;
	const char			*artifact1, *artifact2, *s;

	char				*ptr;
	fsl_id_t			 prid = -1, rid = -1;
	int				 context, rc = 0;

	rc = fcli_process_flags(argv->flags);
	if (rc || (rc = fcli_has_unused_flags(false)))
		return rc;




	/*
	 * If only one artifact is supplied, use the local changes in the
	 * current checkout to diff against it. If no artifacts are supplied,
	 * diff any local changes on disk against the current checkout.
	 */
	if (fcli.argc == 2) {
		artifact1 = fcli_next_arg(true);
		artifact2 = fcli_next_arg(true);
	} else if (fcli.argc < 2) {
		artifact1 = "current";
		rid = 0;
		if (fcli_next_arg(false))
			artifact1 = fcli_next_arg(true);
		if (!fsl_strcmp(artifact1, "current")) {
			if ((rc = fsl_ckout_changes_scan(f)))
				return fcli_err_set(rc,
				    "fsl_ckout_changes_scan");
			if (!fsl_ckout_has_changes(f)) {
				fsl_fprintf(stdout, "No local changes.\n");
				return rc;
			}

		}
	} else { /* fcli_* APIs should prevent getting here but just in case. */
		usage_diff();

		return fcli_err_set(FSL_RC_MISUSE, "\ninvalid args");



	}


	/* Find the corresponding rids for the versions we have; checkout = 0 */
	rc = fsl_sym_to_rid(f, artifact1, FSL_SATYPE_ANY, &prid);
	if (rc || prid < 0)
		return fcli_err_set(rc, "invalid artifact [%s]", artifact1);
	if (rid != 0)
		rc = fsl_sym_to_rid(f, artifact2, FSL_SATYPE_ANY, &rid);
	if (rc || rid < 0)
		return fcli_err_set(rc, "invalid artifact [%s]", artifact2);

	commit = calloc(1, sizeof(*commit));
	if (rid == 0)
		fsl_ckout_version_info(f, NULL, (const char **)commit->uuid);
	else
		commit->uuid = fsl_rid_to_uuid(f, rid);


	commit->puuid = fsl_rid_to_uuid(f, prid);
	commit->rid = rid;

	/*
	 * If either of the supplied versions are not checkin artifacts,
	 * let the user know which operands aren't valid.
	 */
	if ((fsl_rid_is_a_checkin(f, rid) || rid == 0) &&
	    fsl_rid_is_a_checkin(f, prid))
		commit->type = "checkin";
	else
		return fcli_err_set(FSL_RC_TYPE,
		    "artifact(s) [%s] not resolvable to a checkin",
		    (!fsl_rid_is_a_checkin(f, rid) && rid != 0) &&
		    !fsl_rid_is_a_checkin(f, prid) ?
		    fsl_mprintf("%s / %s", artifact1, artifact2) :
		    (!fsl_rid_is_a_checkin(f, rid) && rid != 0) ?
		    artifact2 : artifact1);

	init_curses();

	if (fnc_init.context) {
		s = fnc_init.context;
		context = strtol(s, &ptr, 10);
		if (errno == ERANGE)
			return fsl_cx_err_set(f, FSL_RC_RANGE,
			    "out of range: -x|--context=%s [%s]", s, ptr);
		else if (errno != 0 || errno == EINVAL)
			return fsl_cx_err_set(f, FSL_RC_MISUSE,
			    "not a number: -x|--context=%s [%s]", s, ptr);
		else if (ptr && *ptr != '\0')
			return fsl_cx_err_set(f, FSL_RC_MISUSE,
			    "invalid char: -x|--context=%s [%s]", s, ptr);
		context = MIN(DIFF_MAX_CTXT, context);
	} else
		context = 5;

	view = view_open(0, 0, 0, 0, FNC_VIEW_DIFF);
	if (view == NULL)
		return fcli_err_set(FSL_RC_OOM, "view_open fail");

	rc = open_diff_view(view, commit, context, fnc_init.ws,
	    fnc_init.invert, !fnc_init.quiet, NULL);
	if (rc)
		return fcli_err_set(rc, "open_diff_view fail");
	rc = view_loop(view);

	return rc;
}

static int
cmd_blame(fcli_command const *argv)
{
	/* Not yet implemened. */
	f_out("%s blame is not yet implemented.\n", fcli_progname());
	return FSL_RC_NYI;
}

static void
fnc_show_version(void)
{
	printf("%s %s\n", fcli_progname(), PRINT_VERSION);
}

Deleted include/fossil-scm/Makefile.

1
2
3
all clean:
	$(MAKE) -C ../../ $(MAKECMDGOALS)

<
<
<






Deleted include/fossil-scm/config-win32.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#if !defined(_ORG_FOSSIL_SCM_FSL_AUTO_CONFIG_H_INCLUDED_)
#define _ORG_FOSSIL_SCM_FSL_AUTO_CONFIG_H_INCLUDED_ 1
#define FSL_AUX_SCHEMA "2015-01-24"
#define FSL_CONTENT_SCHEMA "2"
#define FSL_PACKAGE_NAME "libfossil"
#define FSL_LIBRARY_VERSION "0.0.1-alphabeta"
/* Tweak the following for your system... */
#define HAVE_GETADDRINFO 1
#define HAVE_INET_NTOP 1
#if !defined(_WIN32)
#define HAVE_DLFCN_H 1
#define HAVE_DLOPEN 1
#define HAVE_LIBDL 1
#define HAVE_LIBLTDL 0
#define HAVE_LSTAT 1
#define HAVE_LTDL_H 0
#define HAVE_LT_DLOPEN 0
#define HAVE_OPENDIR 1
#define HAVE_PIPE 1
#define HAVE_STAT 1
#ifndef _BSD_SOURCE
#define _BSD_SOURCE 1
#endif
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 500
#endif
#else
#define HAVE_DLFCN_H 0
#define HAVE_DLOPEN 0
#define HAVE_LIBDL 0
#define HAVE_LIBLTDL 0
#define HAVE_LSTAT 0
#define HAVE_LTDL_H 0
#define HAVE_LT_DLOPEN 0
#define HAVE_OPENDIR 1
#define HAVE_PIPE 0
#define HAVE_STAT 0
#endif
/*_WIN32*/


#if defined(_MSC_VER)
#define FSL_PLATFORM_OS "windows"
#define FSL_PLATFORM_PLATFORM "windows"
#define FSL_PLATFORM_PATH_SEPARATOR ";"
#define FSL_CKOUTDB_NAME "./_FOSSIL_"
//define a __func__ compatibility macro
#if _MSC_VER < 1500	//(vc9.0; dev studio 2008)
//sorry; cant do much better than nothing at all on those earlier ones
#define __func__ "(func)"
#else
#define __func__ __FUNCTION__
#endif
//for the time being at least, don't complain about there being secure crt alternatives:
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif
//for the time being at least, don't complain about using POSIX names instead of ISO C++:
#pragma warning ( disable : 4996 )
//for the time being at least, suppresss some int conversion warnings
#pragma warning ( disable : 4244 )		//'fsl_size_t' to 'int'; this masks other problems that should be fixed
#pragma warning ( disable : 4761 )		//'integral size mismatch in argument'; more size_t problems
#pragma warning ( disable : 4267 )		//'size_t' to 'int'; crops up especially in 64-bit builds
//these were extracted from fossil's unistd.h
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#include <io.h>
#elif defined(__MINGW32__)
#define FSL_PLATFORM_OS "mingw"
#define FSL_PLATFORM_PLATFORM "windows"
#define FSL_PLATFORM_PATH_SEPARATOR ";"
#define FSL_CKOUTDB_NAME "./.fslckout"
#elif defined(__CYGWIN__)
#define FSL_PLATFORM_OS "cygwin"
#define FSL_PLATFORM_PLATFORM "unix"
#define FSL_PLATFORM_PATH_SEPARATOR ":"
#define FSL_CKOUTDB_NAME "./_FOSSIL_"
#else
#define FSL_PLATFORM_OS "unknown"
#define FSL_PLATFORM_PLATFORM "unix"
#define FSL_PLATFORM_PATH_SEPARATOR ":"
#define FSL_CKOUTDB_NAME "./.fslckout"
#endif


#endif
/* _ORG_FOSSIL_SCM_FSL_AUTO_CONFIG_H_INCLUDED_ */

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<














































































































































































Deleted include/fossil-scm/fossil-auth.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/* -*- 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_AUTH_H_INCLUDED)
#define ORG_FOSSIL_SCM_FSL_AUTH_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 public APIs for handling fossil
  authentication-related tasks.
*/

#include "fossil-core.h"

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

/**
   If f has an opened repository, this function forms a hash from:

   "ProjectCode/zLoginName/zPw"

   (without the quotes)

   where ProjectCode is a repository-instance-dependent series of
   random bytes. The returned string is owned by the caller, who
   must eventually fsl_free() it. The project code is stored in
   the repository's config table under the key 'project-code', and
   this routine fetches that key if necessary.

   Potential TODO:

   - in fossil(1), this function generates a different result (it
   returns a copy of zPw) if the project code is not set, under
   the assumption that this is "the first xfer request of a
   clone."  Whether or not that will apply at this level to
   libfossil remains to be seen.

   TODO? Does fossil still use SHA1 for this?
*/
FSL_EXPORT char * fsl_sha1_shared_secret( fsl_cx * f, char const * zLoginName, char const * zPw );

/**
   Fetches the login group name (if any) for the given context's
   current repositorty db. If f has no opened repo, 0 is returned.

   If the repo belongs to a login group, its name is returned in the
   form of a NUL-terminated string. The returned value (which may be
   0) is owned by the caller, who must eventually fsl_free() it. The
   value (unlike in fossil(1)) is not cached because it may change
   via modification of the login group.
*/
FSL_EXPORT char * fsl_repo_login_group_name(fsl_cx * f);

/**
   Fetches the login cookie name associated with the current repository
   db, or 0 if no repository is opened.

   The returned (NUL-terminated) string is owned by the caller, who
   must eventually fsl_free() it. The value is not cached in f because
   it may change during the lifetime of a repo (if a login group is
   set or removed).

   The login cookie name is a string in the form "fossil-XXX", where
   XXX is the first 16 hex digits of either the repo's
   'login-group-code' or 'project-code' config values (in that order).
*/
FSL_EXPORT char * fsl_repo_login_cookie_name(fsl_cx * f);

/**
   Searches for a user ID (from the repo.user.uid DB field) for a given
   username and password. The password may be either its hashed form or
   non-hashed form (if it is not exactly 40 bytes long, that is!).

   On success, 0 is returned and *pId holds the ID of the
   user found (if any).  *pId will be set to 0 if no match for the
   name/password was found, or positive if a match was found.

   If any of the arguments are NULL, FSL_RC_MISUSE is returned. f must
   have an opened repo, else FSL_RC_NOT_A_REPO is returned.

*/
FSL_EXPORT int fsl_repo_login_search_uid(fsl_cx * f, char const * zUsername,
                                         char const * zPasswd, fsl_id_t * pId);

/**
   Clears all login state for the given user ID. If the ID is <=0 then
   ALL logins are cleared. Has no effect on the built-in pseudo-users.

   Returns non-0 on error, and not finding a matching user ID is not
   considered an error.

   f must have an opened repo, or FSL_RC_NOT_A_REPO is returned.

   TODO: there are currently no APIs for _setting_ the state this
   function clears!
*/
FSL_EXPORT int fsl_repo_login_clear( fsl_cx * f, fsl_id_t userId );


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


































































































































































































































Deleted include/fossil-scm/fossil-checkout.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
/* -*- 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_CHECKOUT_H_INCLUDED)
#define ORG_FOSSIL_SCM_FSL_CHECKOUT_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).
*/

/** @file fossil-checkout.h

    fossil-checkout.h declares APIs specifically dealing with
    checkout-side state, as opposed to purely repository-db-side state
    or non-content-related APIs.
*/

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

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


/**
   Returns version information for the current checkout.

   If f has an opened checkout then...

   If uuid is not NULL then *uuid is set to the UUID of the opened
   checkout, or NULL if there is no checkout. If rid is not NULL, *rid
   is set to the record ID of that checkout, or 0 if there is no
   checkout (or the current checkout is from an empty repository). The
   returned uuid bytes and rid are owned by f and valid until the
   library updates its checkout state to a newer checkout version
   (essentially unpredictably). When in doubt about lifetime issues,
   copy the UUID immediately after calling this if they will be needed
   later.

   Corner case: a new repo with no checkins has an RID of 0 and a UUID
   of NULL. That does not happen with fossil-generated repositories,
   as those always "seed" the database with an initial commit artifact
   containing no files.
*/
FSL_EXPORT void fsl_ckout_version_info(fsl_cx *f, fsl_id_t * rid,
                                       fsl_uuid_cstr * uuid );

/**
   Given a fsl_cx with an opened checkout, and a filename, this
   function canonicalizes zOrigName to a form suitable for use as
   an in-repo filename, _appending_ the results to pOut. If pOut is
   NULL, it performs its normal checking but does not write a
   result, other than to return 0 for success.

   As a special case, if zOrigName refers to the top-level checkout
   directory, it resolves to either "." or "./", depending on whether
   zOrigName contains a trailing slash.

   If relativeToCwd is true then the filename is canonicalized
   based on the current working directory (see fsl_getcwd()),
   otherwise f's current checkout directory is used as the virtual
   root.

   If the input name contains a trailing slash, it is retained in
   the output sent to pOut except in the top-dir case mentioned
   above.

   Returns 0 on success, meaning that the value appended to pOut
   (if not NULL) is a syntactically valid checkout-relative path.

   Returns FSL_RC_RANGE if zOrigName points to a path outside
   of f's current checkout root.

   Returns FSL_RC_NOT_A_CKOUT if f has no checkout opened.

   Returns FSL_RC_MISUSE if !zOrigName, FSL_RC_OOM on an allocation
   error.

   This function does not validate whether or not the file actually
   exists, only that its name is potentially valid as a filename
   for use in a checkout (though other, downstream rules might prohibit that, e.g.
   the filename "..../...." is not valid but is not seen as invalid by
   this function). (Reminder to self: we could run the end result through
   fsl_is_simple_pathname() to catch that?)
*/
FSL_EXPORT int fsl_ckout_filename_check( fsl_cx * f, bool relativeToCwd,
                                         char const * zOrigName, fsl_buffer * pOut );

/**
   Callback type for use with fsl_ckout_manage_opt(). It should
   inspect the given filename using whatever criteria it likes, set
   *include to true or false to indicate whether the filename is okay
   to include the current add-file-to-repo operation, and return 0.

   If it returns non-0 the add-file-to-repo process will end and that
   error code will be reported to its caller. Such result codes must
   come from the FSL_RC_xxx family.

   It will be passed a name which is relative to the top-most checkout
   directory.

   The final argument is not used by the library, but is passed on
   as-is from the fsl_ckout_manage_opt::callbackState pointer which
   is passed to fsl_ckout_manage().
*/
typedef int (*fsl_ckout_manage_f)(const char *zFilename,
                                    bool *include,
                                    void *state);
/**
   Options for use with fsl_ckout_manage().
*/
struct fsl_ckout_manage_opt {
  /**
     The file or directory name to add. If it is a directory, the add
     process will recurse into it.
  */
  char const * filename;
  /**
     Whether to evaluate the given name as relative to the current working
     directory or to the current checkout root.

     This makes a subtle yet important difference in how the name is
     resolved. CLI apps which take file names from the user from
     within a checkout directory will generally want to set
     relativeToCwd to true. GUI apps, OTOH, will possibly need it to
     be false, depending on how they resolve and pass on the
     filenames.
  */
  bool relativeToCwd;
  /**
     Whether or not to check the name(s) against the 'ignore-globs'
     config setting (if set).
  */     
  bool checkIgnoreGlobs;
  /**
     Optional predicate function which may be called for each
     to-be-added filename. It is only called if:

     - It is not NULL (obviously) and...

     - The file is not already in the checkout database and...

     - The is-internal-name check passes (see
     fsl_reserved_fn_check()) and...

     - If checkIgnoreGlobs is false or the name does not match one of
     the ignore-globs values.

     The name it is passed is relative to the checkout root.

     Because the callback is called only if other options have not
     already excluded the file, the client may use the callback to
     report to the user (or otherwise record) exactly which files
     get added.
  */
  fsl_ckout_manage_f callback;

  /**
     State to be passed to this->callback.
  */
  void * callbackState;

  /**
     These counts are updated by fsl_ckout_manage() to report
     what it did.
  */
  struct {
    /**
       Number of files actually added by fsl_ckout_manage().
    */
    uint32_t added;
    /**
       Number of files which were requested to be added but were only
       updated because they had previously been added. Updates set the
       vfile table entry's current mtime, executable-bit state, and
       is-it-a-symlink state. (That said, this code currently ignores
       symlinks altogether!)
    */
    uint32_t updated;
    /**
       The number of files skipped over for addition. This includes
       files which meet any of these criteria:

       - fsl_reserved_fn_check() fails.

       - If the checkIgnoreGlobs option is true and a filename matches
         any of those globs.

       - The client-provided callback says not to include the file.
    */
    uint32_t skipped;
  } counts;
};
typedef struct fsl_ckout_manage_opt fsl_ckout_manage_opt;
/**
   Initialized-with-defaults fsl_ckout_manage_opt instance,
   intended for use in const-copy initialization.
*/
#define fsl_ckout_manage_opt_empty_m {\
    NULL/*filename*/, true/*relativeToCwd*/, true/*checkIgnoreGlobs*/, \
    NULL/*callback*/, NULL/*callbackState*/,                         \
    {/*counts*/ 0/*added*/, 0/*updated*/, 0/*skipped*/}               \
  }
/**
   Initialized-with-defaults fsl_ckout_manage_opt instance,
   intended for use in non-const copy initialization.
*/
FSL_EXPORT const fsl_ckout_manage_opt fsl_ckout_manage_opt_empty;

/**
   Adds the given filename or directory (recursively) to the current
   checkout vfile list of files as a to-be-added file, or updates an
   existing record if one exists.

   This function ensures that opt->filename gets canonicalized and can
   be found under the checkout directory, and fails if no such file
   exists (checking against the canonicalized name). Filenames are all
   filtered through fsl_reserved_fn_check() and may have other filters
   applied to them, as determined by the options object.

   Each filename which passes through the filters is passed to
   the opt->callback (if not NULL), which may perform a final
   filtering check and/or alert the client about the file being
   queued.

   The options object is non-const because this routine updates
   opt->counts when it adds, updates, or skips a file. On each call,
   it updated opt->counts without resetting it (as this function is
   typically called in a loop). This function does not modify any
   other entries of that object and it requires that the object not be
   modified (e.g. via opt->callback()) while it is recursively
   processing. To reset the counts between calls, if needed:

   @code
   opt->counts = fsl_ckout_manage_opt_empty.counts;
   @endcode

   Returns 0 on success, non-0 on error.

   Files queued for addition this way can be unqueued before they are
   committed using fsl_ckout_unmanage().

   @see fsl_ckout_unmanage()
   @see fsl_reserved_fn_check()
*/
FSL_EXPORT int fsl_ckout_manage( fsl_cx * f,
                                   fsl_ckout_manage_opt * opt );


/**
   Callback type for use with fsl_ckout_unmanage(). It is called
   by the removal process, immediately after a file is "removed"
   from SCM management (a.k.a. when the file becomes "unmanaged").

   If it returns non-0 the unmanage process will end and that
   error code will be reported to its caller. Such result codes must
   come from the FSL_RC_xxx family.

   It will be passed a name which is relative to the top-most checkout
   directory. The client is free to unlink the file from the filesystem
   if they like - the library does not do so automatically

   The final argument is not used by the library, but is passed on
   as-is from the callbackState pointer which
   is passed to fsl_ckout_unmanage().
*/
typedef int (*fsl_ckout_unmanage_f)(const char *zFilename, void *state);

/**
   Options for use with fsl_ckout_unmanage().
*/
struct fsl_ckout_unmanage_opt {
  /**
     The file or directory name to add. If it is a directory, the add
     process will recurse into it. See also this->vfileIds.
  */
  char const * filename;
  /**
     An alternative to assigning this->filename is to point
     this->vfileIds to a bag of vfile.id values. If this member is not
     NULL, fsl_ckout_revert() will ignore this->filename.

     @see fsl_filename_to_vfile_ids()
  */
  fsl_id_bag const * vfileIds;
  /**
     Whether to evaluate this->filename as relative to the current
     working directory (true) or to the current checkout root
     (false). This is ignored when this->vfileIds is not NULL.

     This makes a subtle yet important difference in how the name is
     resolved. CLI apps which take file names from the user from
     within a checkout directory will generally want to set
     relativeToCwd to true. GUI apps, OTOH, will possibly need it to
     be false, depending on how they resolve and pass on the
     filenames.
  */
  bool relativeToCwd;
  /**
     If true, fsl_vfile_changes_scan() is called to ensure that
     the filesystem and vfile tables agree. If the client code has
     called that function, or its equivalent, since any changes were
     made to the checkout then this may be set to false to speed up
     the rm process.
  */
  bool scanForChanges;
  /**
     Optional predicate function which will be called after each
     file is made unmanaged.

     The name it is passed is relative to the checkout root.
  */
  fsl_ckout_unmanage_f callback;
  /**
     State to be passed to this->callback.
  */
  void * callbackState;

};
typedef struct fsl_ckout_unmanage_opt fsl_ckout_unmanage_opt;
/**
   Initialized-with-defaults fsl_ckout_unmanage_opt instance,
   intended for use in const-copy initialization.
*/
#define fsl_ckout_unmanage_opt_empty_m {\
    NULL/*filename*/, NULL/*vfileIds*/,\
    true/*relativeToCwd*/,true/*scanForChanges*/, \
    NULL/*callback*/, NULL/*callbackState*/ \
}
/**
   Initialized-with-defaults fsl_ckout_unmanage_opt instance,
   intended for use in non-const copy initialization.
*/
FSL_EXPORT const fsl_ckout_unmanage_opt fsl_ckout_unmanage_opt_empty;

/**
   The converse of fsl_ckout_manage(), this queues a file for removal
   from the current checkout. Unlike fsl_ckout_manage(), this routine
   does not ensure that opt->filename actually exists - it only
   normalizes zFilename into its repository-friendly form and passes
   it through the vfile table.

   If opt->filename refers to a directory then this operation queues
   all files under that directory (recursively) for removal. In this
   case, it is irrelevant whether or not opt->filename ends in a
   trailing slash.

   Returns 0 on success, any of a number of non-0 codes on error.
   Returns FSL_RC_MISUSE if !opt->filename or !*opt->filename.
   Returns FSL_RC_NOT_A_CKOUT if f has no opened checkout.

   If opt->callback is not NULL, it is called for each
   newly-unamanaged entry. The intention is to provide it the
   opportunity to notify the user, record the filename for later use,
   remove the file from the filesystem, etc. If it returns non-0, the
   unmanaging process will fail with that code and any pending
   transaction will be placed into a rollback state.

   This routine does not actually remove any files from the
   filesystem, it only modifies the vfile table entry so that the
   file(s) will be removed from the SCM by the commit process. If
   opt->filename is an entry which was previously
   fsl_ckout_manage()'d, but not yet committed, or any such entries
   are found under directory opt->filename, they are removed from the
   vfile table. i.e. this effective undoes the add operation.

   @see fsl_ckout_manage()
*/
FSL_EXPORT int fsl_ckout_unmanage( fsl_cx * f,
                                  fsl_ckout_unmanage_opt const * opt );

/**
   Hard-coded range of values of the vfile.chnged db field.
   These values are part of the fossil schema and must not
   be modified.
*/
enum fsl_vfile_change_e {
  /** File is unchanged. */
  FSL_VFILE_CHANGE_NONE = 0,
  /** File edit. */
  FSL_VFILE_CHANGE_MOD = 1,
  /** File changed due to a merge. */
  FSL_VFILE_CHANGE_MERGE_MOD = 2,
  /** File added by a merge. */
  FSL_VFILE_CHANGE_MERGE_ADD = 3,
  /** File changed due to an integrate merge. */
  FSL_VFILE_CHANGE_INTEGRATE_MOD = 4,
  /** File added by an integrate merge. */
  FSL_VFILE_CHANGE_INTEGRATE_ADD = 5,
  /** File became executable but has unmodified contents. */
  FSL_VFILE_CHANGE_IS_EXEC = 6,
  /** File became a symlink whose target equals its old contents. */
  FSL_VFILE_CHANGE_BECAME_SYMLINK = 7,
  /** File lost executable status but has unmodified contents. */
  FSL_VFILE_CHANGE_NOT_EXEC = 8,
  /** File lost symlink status and has contents equal to its old target. */
  FSL_VFILE_CHANGE_NOT_SYMLINK = 9
};
typedef enum fsl_vfile_change_e fsl_vfile_change_e;

/**
   Change-type flags for use with fsl_ckout_changes_visit() and
   friends.

   TODO: consolidate this with fsl_vfile_change_e insofar as possible.
   There are a few checkout change statuses not reflected in
   fsl_vfile_change_e.
*/
enum fsl_ckout_change_e {
/**
   Sentinel placeholder value.
*/
FSL_CKOUT_CHANGE_NONE = 0,
/**
   Indicates that a file was modified in some unspecified way.
*/
FSL_CKOUT_CHANGE_MOD = FSL_VFILE_CHANGE_MOD,
/**
   Indicates that a file was modified as the result of a merge.
*/
FSL_CKOUT_CHANGE_MERGE_MOD = FSL_VFILE_CHANGE_MERGE_MOD,
/**
   Indicates that a file was added as the result of a merge.
*/
FSL_CKOUT_CHANGE_MERGE_ADD = FSL_VFILE_CHANGE_MERGE_ADD,
/**
   Indicates that a file was modified as the result of an
   integrate-merge.
*/
FSL_CKOUT_CHANGE_INTEGRATE_MOD = FSL_VFILE_CHANGE_INTEGRATE_MOD,
/**
   Indicates that a file was added as the result of an
   integrate-merge.
*/
FSL_CKOUT_CHANGE_INTEGRATE_ADD = FSL_VFILE_CHANGE_INTEGRATE_ADD,
/**
   Indicates that the file gained the is-executable trait
   but is otherwise unmodified.
*/
FSL_CKOUT_CHANGE_IS_EXEC = FSL_VFILE_CHANGE_IS_EXEC,
/**
   Indicates that the file has changed to a symlink.
*/
FSL_CKOUT_CHANGE_BECAME_SYMLINK = FSL_VFILE_CHANGE_BECAME_SYMLINK,
/**
   Indicates that the file lost the is-executable trait
   but is otherwise unmodified.
*/
FSL_CKOUT_CHANGE_NOT_EXEC = FSL_VFILE_CHANGE_NOT_EXEC,
/**
   Indicates that the file was previously a symlink but is
   now a plain file.
*/
FSL_CKOUT_CHANGE_NOT_SYMLINK = FSL_VFILE_CHANGE_NOT_SYMLINK,
/**
   Indicates that a file was added.
*/
FSL_CKOUT_CHANGE_ADDED = FSL_CKOUT_CHANGE_NOT_SYMLINK + 1000,
/**
   Indicates that a file was removed from SCM management.
*/
FSL_CKOUT_CHANGE_REMOVED,
/**
   Indicates that a file is missing from the local checkout.
*/
FSL_CKOUT_CHANGE_MISSING,
/**
   Indicates that a file was renamed.
*/
FSL_CKOUT_CHANGE_RENAMED
};

typedef enum fsl_ckout_change_e fsl_ckout_change_e;

/**
   This is equivalent to calling fsl_vfile_changes_scan() with the
   arguments (f, -1, 0).

   @see fsl_ckout_changes_visit()
   @see fsl_vfile_changes_scan()
*/
FSL_EXPORT int fsl_ckout_changes_scan(fsl_cx * f);

/**
   A typedef for visitors of checkout status information via
   fsl_ckout_changes_visit(). Implementions will receive the
   last argument passed to fsl_ckout_changes_visit() as their
   first argument. The second argument indicates the type of change
   and the third holds the repository-relative name of the file.

   If changes is FSL_CKOUT_CHANGE_RENAMED then origName will hold
   the original name, else it will be NULL.

   Implementations must return 0 on success, non-zero on error. On
   error any looping performed by fsl_ckout_changes_visit() will
   stop and this function's result code will be returned.

   @see fsl_ckout_changes_visit()
*/
typedef int (*fsl_ckout_changes_f)(void * state, fsl_ckout_change_e change,
                                      char const * filename,
                                      char const * origName);

/**
   Compares the changes of f's local checkout against repository
   version vid (checkout version if vid is negative). For each
   change detected it calls visitor(state,...) to report the
   change.  If visitor() returns non-0, that code is returned from
   this function. If doChangeScan is true then
   fsl_ckout_changes_scan() is called by this function before
   iterating, otherwise it is assumed that the caller has called
   that or has otherwise ensured that the checkout db's vfile table
   has been populated.

   If the callback returns FSL_RC_BREAK, this function stops iteration
   and returns 0.

   Returns 0 on success.

   @see fsl_ckout_changes_scan()
*/
FSL_EXPORT int fsl_ckout_changes_visit( fsl_cx * f, fsl_id_t vid,
                                           bool doChangeScan,
                                           fsl_ckout_changes_f visitor,
                                           void * state );
/**
   A bitmask of flags for fsl_vfile_changes_scan().
*/
enum fsl_ckout_sig_e {
/**
   The empty flags set.
*/
FSL_VFILE_CKSIG_NONE = 0,

/**
   Non-file/non-link FS objects trigger an error.
*/
FSL_VFILE_CKSIG_ENOTFILE = 0x001,
/**
   Verify file content using hashing, regardless of whether or not
   file timestamps differ.
*/
FSL_VFILE_CKSIG_HASH = 0x002,
/**
   For unchanged or changed-by-merge files, set the mtime to last
   check-out time, as determined by fsl_mtime_of_manifest_file().
*/
FSL_VFILE_CKSIG_SETMTIME = 0x004,
/**
   Indicates that when populating the vfile table, it should be not be
   cleared of entries for other checkins. Normally we want to clear
   all versions except for the one we're working with, but at least
   a couple of use cases call for having multiple versions in vfile at
   once. Many algorithms generally assume only a single checkin's
   worth of state is in vfile and can get confused if that is not the
   case.
*/
FSL_VFILE_CKSIG_KEEP_OTHERS = 0x008,

/**
   If set and fsl_vfile_changes_scan() is passed a version other than
   the pre-call checkout version, it will, when finished, write the
   given version in the "checkout" setting of the ckout.vvar table,
   effectively switching the checkout to that version. It does not do
   this be default because it is sometimes necessary to have two
   versions in the vfile table at once and the operation doing so
   needs to control which version number is the current checkout.
*/
FSL_VFILE_CKSIG_WRITE_CKOUT_VERSION = 0x010
};

/**
    This function populates (if needed) the vfile table of f's
    checkout db for the given checkin version ID then compares files
    listed in it against files in the checkout directory, updating
    vfile's status for the current checkout version id as its goes. If
    vid is<=0 then the current checkout's RID is used in its place
    (note that 0 is the RID of an initial empty repository!).

    cksigFlags must be 0 or a bitmask of fsl_ckout_sig_e values.

    This is a relatively memory- and filesystem-intensive operation,
    and should not be performed more often than necessary. Many SCM
    algorithms rely on its state being correct, however, so it's
    generally better to err on the side of running it once too often
    rather than once too few times.

    Returns 0 on success, non-0 on error.

    BUG: this does not properly catch one particular corner-case
    change, where a file has been replaced by a same-named non-file
    (symlink or directory).
*/
FSL_EXPORT int fsl_vfile_changes_scan(fsl_cx * f, fsl_id_t vid,
                                      unsigned cksigFlags);

/**
   If f has an opened checkout which has local changes noted in its
   checkout db state (the vfile table), returns true, else returns
   false. Note that this function does not do the filesystem scan to
   check for changes, but checks only the db state. Use
   fsl_vfile_changes_scan() to perform the actual scan (noting that
   library-side APIs which update that state may also record
   individual changes or automatically run a scan).
*/
FSL_EXPORT bool fsl_ckout_has_changes(fsl_cx *f);

/**
   Callback type for use with fsl_checkin_queue_opt for alerting a
   client about exactly which files get enqueued/dequeued via
   fsl_checkin_enqueue() and fsl_checkin_dequeue().

   This function gets passed the checkout-relative name of the file
   being enqueued/dequeued and the client-provided state pointer which
   was passed to the relevant API. It must return 0 on success. If it
   returns non-0, the API on whose behalf this callback is invoked
   will propagate that error code back to the caller.

   The intent of this callback is simply to report changes to the
   client, not to perform validation. Thus such callbacks "really
   should not fail" unless, e.g., they encounter an OOM condition or
   some such. Any validation required by the client should be
   performed before calling fsl_checkin_enqueue()
   resp. fsl_checkin_dequeue().
*/
typedef int (*fsl_checkin_queue_f)(const char * filename, void * state);

/**
   Options object type used by fsl_checkin_enqueue() and
   fsl_checkin_dequeue().
*/
struct fsl_checkin_queue_opt {
  /**
     File or directory name to enqueue/dequeue to/from a pending
     checkin.
  */
  char const * filename;
  /**
     If true, filename (if not absolute) is interpreted as relative to
     the current working directory, else it is assumed to be relative
     to the top of the current checkout directory.
  */
  bool relativeToCwd;

  /**
     If not NULL then this->filename and this->relativeToCwd are
     IGNORED and any to-queue filename(s) is/are added from this
     container. It is an error (FSL_RC_MISUSE) to pass an empty bag.
     (Should that be FSL_RC_RANGE instead?)

     The bag is assumed to contain values from the vfile.id checkout
     db field, refering to one or more files which should be queued
     for the pending checkin. It is okay to pass IDs for unmodified
     files or to queue the same files multiple times. Unmodified files
     may be enqueued but will be ignored by the checkin process if, at
     the time the checkin is processed, they are still unmodified.
     Duplicated entries are simply ignored for the 2nd and subsequent
     inclusion.

     @see fsl_ckout_vfile_ids()
  */
  fsl_id_bag const * vfileIds;

  /**
     If true, fsl_vfile_changes_scan() is called to ensure that the
     filesystem and vfile tables agree. If the client code has called
     that function, or its equivalent, since any changes were made to
     the checkout then this may be set to false to speed up the
     enqueue process. This is only used by fsl_checkin_enqueue(), not
     fsl_checkin_dequeue().
  */
  bool scanForChanges;

  /**
     If true, only flagged-as-modified files will be enqueued by
     fsl_checkin_enqueue(). By and large, this should be set to
     true. Setting this to false is generally only intended/useful for
     testing.
  */
  bool onlyModifiedFiles;

  /**
     It not NULL, is pass passed the checkout-relative filename of
     each enqueued/dequeued file and this->callbackState. See the
     callback type's docs for more details.
  */
  fsl_checkin_queue_f callback;

  /**
     Opaque client-side state for use as the 2nd argument to
     this->callback.
  */
  void * callbackState;
};

/** Convenience typedef. */
typedef struct fsl_checkin_queue_opt fsl_checkin_queue_opt;

/** Initialized-with-defaults fsl_checkin_queue_opt structure, intended for
    const-copy initialization. */
#define fsl_checkin_queue_opt_empty_m { \
  NULL/*filename*/,true/*relativeToCwd*/,    \
  NULL/*vfileIds*/,                                 \
  true/*scanForChanges*/,true/*onlyModifiedFiles*/,   \
  NULL/*callback*/,NULL/*callbackState*/      \
}

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

/**
   Adds one or more files to f's list of "selected" files - those
   which should be included in the next commit (see
   fsl_checkin_commit()).

   Warning: if this function is not called before
   fsl_checkin_commit(), then fsl_checkin_commit() will select all
   modified, fsl_ckout_manage()'d, fsl_ckout_unmanage()'d, or renamed
   files by default.

   opt->filename must be a non-empty NUL-terminated string. The
   filename is canonicalized via fsl_ckout_filename_check() - see that
   function for the meaning of the opt->relativeToCwd parameter. To
   queue all modified files in a checkout, set opt->filename to ".",
   opt->relativeToCwd to false, and opt->onlyModifiedFiles to true.
   "Modified" includes any which are pending deletion, are
   newly-added, or for which a rename is pending.

   The resolved name must refer to either a single vfile.pathname
   value in the current vfile table or to a checkout-root-relative
   directory. All matching filenames which refer to modified files (as
   recorded in the vfile table) are queued up for the next commit.
   If opt->filename is NULL, empty, or ("." and opt->relativeToCwd is false)
   then all files in the vfile table are checked for changes.

   If opt->scanForChanges is true then fsl_vfile_changes_scan() is
   called before starting to ensure that the vfile entries are up to
   date. If the client app has "recently" run that (or its
   equivalent), that (slow) step can be skipped by setting
   opt->scanForChanges to false before calling this

   Note that after this returns, any given file may still be modified
   by the client before the commit takes place, and the changes on
   disk at the point of the fsl_checkin_commit() are the ones which
   get saved (or not).

   For each resolved entry which actually gets enqueued (i.e. was not
   already enqueued and which is marked as modified), opt->callback
   (if it is not NULL) is passed the checkout-relative file name and
   the opt->callbackState pointer.

   Returns 0 on success, FSL_RC_MISUSE if either pointer is NULL, or
   *zName is NUL. Returns FSL_RC_OOM on allocation error. It is not
   inherently an error for opt->filename to resolve to no queue-able
   entries. A client can check for that case, if needed, by assigning
   opt->callback and incrementing a counter in that callback. If the
   callback is never called, no queue-able entries were found.

   On error f's error state might (depending on the nature of the
   problem) contain more details.

   @see fsl_checkin_is_enqueued()
   @see fsl_checkin_dequeue()
   @see fsl_checkin_discard()
   @see fsl_checkin_commit()
*/
FSL_EXPORT int fsl_checkin_enqueue(fsl_cx * f,
                                   fsl_checkin_queue_opt const * opt);

/**
   The opposite of fsl_checkin_enqueue(), this opt->filename,
   which may resolve to a single name or a directory, from the checkin
   queue. Returns 0 on succes. This function does no validation on
   whether a given file(s) actually exist(s), it simply asks the
   internals to clean up matching strings from the checkout's vfile
   table. Specifically, it does not return an error if this operation
   finds no entries to dequeue.

   If opt->filename is empty or NULL then ALL files are unqueued from
   the pending checkin.

   If opt->relativeToCwd is true (non-0) then opt->filename is
   resolved based on the current directory, otherwise it is resolved
   based on the checkout's root directory.

   If opt->filename is not NULL or empty, this functions runs the
   given path through fsl_ckout_filename_check() and will fail if that
   function fails, propagating any error from that function. Ergo,
   opt->filename must refer to a path within the current checkout.

   @see fsl_checkin_enqueue()
   @see fsl_checkin_is_enqueued()
   @see fsl_checkin_discard()
   @see fsl_checkin_commit()
*/
FSL_EXPORT int fsl_checkin_dequeue(fsl_cx * f,
                                        fsl_checkin_queue_opt const * opt);

/**
   Returns true (non-0) if the file named by zName is in f's current
   file checkin queue.  If NO files are in the current selection
   queue then this routine assumes that ALL files are implicitely
   selected. As long as at least one file is enqueued (via
   fsl_checkin_enqueue()) then this function only returns true
   for files which have been explicitly enqueued.

   If relativeToCwd then zName is resolved based on the current
   directory, otherwise it assumed to be related to the checkout's
   root directory.

   This function returning true does not necessarily indicate that
   the file _will_ be checked in at the next commit. If the file has
   not been modified at commit-time then it will not be part of the
   commit.

   This function honors the fsl_cx_is_case_sensitive() setting
   when comparing names.

   Achtung: this does not resolve directory names like
   fsl_checkin_enqueue() and fsl_checkin_dequeue() do. It
   only works with file names.

   @see fsl_checkin_enqueue()
   @see fsl_checkin_dequeue()
   @see fsl_checkin_discard()
   @see fsl_checkin_commit()
*/
FSL_EXPORT bool fsl_checkin_is_enqueued(fsl_cx * f, char const * zName,
                                        bool relativeToCwd);

/**
   Discards any state accumulated for a pending checking,
   including any files queued via fsl_checkin_enqueue()
   and tags added via fsl_checkin_T_add().

   @see fsl_checkin_enqueue()
   @see fsl_checkin_dequeue()
   @see fsl_checkin_is_enqueued()
   @see fsl_checkin_commit()
   @see fsl_checkin_T_add()
*/
FSL_EXPORT void fsl_checkin_discard(fsl_cx * f);

/**
   Parameters for fsl_checkin_commit().

   Checkins are created in a multi-step process:

   - fsl_checkin_enqueue() queues up a file or directory for
   commit at the next commit.

   - fsl_checkin_dequeue() removes an entry, allowing
   UIs to toggle files in and out of a checkin before
   committing it.

   - fsl_checkin_is_enqueued() can be used to determine whether
   a given name is already enqueued or not.

   - fsl_checkin_T_add() can be used to T-cards (tags) to a
   deck. Branch tags are intended to be applied via the
   fsl_checkin_opt::branch member.

   - fsl_checkin_discard() can be used to cancel any pending file
   enqueuings, effectively cancelling a commit (which can be
   re-started by enqueuing another file).

   - fsl_checkin_commit() creates a checkin for the list of enqueued
   files (defaulting to all modified files in the checkout!). It
   takes an object of this type to specify a variety of parameters
   for the check.

   Note that this API uses the terms "enqueue" and "unqueue" rather
   than "add" and "remove" because those both have very specific
   (and much different) meanings in the overall SCM scheme.
*/
struct fsl_checkin_opt {
  /**
     The commit message. May not be empty - the library
     forbids empty checkin messages.
  */
  char const * message;

  /**
     The optional mime type for the message. Only set
     this if you know what you're doing.
  */
  char const * messageMimeType;

  /**
     The user name for the checkin. If NULL or empty, it defaults to
     fsl_cx_user_get(). If that is NULL, a FSL_RC_RANGE error is
     triggered.
  */
  char const * user;

  /**
     If not NULL, makes the checkin the start of a new branch with
     this name.
  */
  char const * branch;

  /**
     If this->branch is not NULL, this is applied as its "bgcolor"
     propagating property. If this->branch is NULL then this is
     applied as a one-time color tag to the checkin.

     It must be NULL, empty, or in a form usable by HTML/CSS,
     preferably \#RRGGBB form. Length-0 values are ignored (as if
     they were NULL).
  */
  char const * bgColor;

  /**
     If true, the checkin will be marked as private, otherwise it
     will be marked as private or public, depending on whether or
     not it inherits private content.
  */
  bool isPrivate;

  /**
     Whether or not to calculate an R-card. Doing so is very
     expensive (memory and I/O) but it adds another layer of
     consistency checking to manifest files. In practice, the R-card
     is somewhat superfluous and the cost of calculating it has
     proven painful on very large repositories. fossil(1) creates an
     R-card for all checkins but does not require that one be set
     when it reads a manifest.
  */
  bool calcRCard;

  /**
     Tells the checkin to close merged-in branches (merge type of
     0). INTEGRATE merges (type=-4) are always closed by a
     checkin. This does not apply to CHERRYPICK (type=-1) and
     BACKOUT (type=-2) merges.
  */
  bool integrate;

  /**
     If true, allow a file to be checked in if it contains
     fossil-style merge conflict markers, else fail if an attempt is
     made to commit any files with such markers.
  */
  bool allowMergeConflict;

  /**
     A hint to fsl_checkin_commit() about whether it needs to scan the
     checkout for changes. Set this to false ONLY if the calling code
     calls fsl_ckout_changes_scan() (or equivalent,
     e.g. fsl_vfile_changes_scan()) immediately before calling
     fsl_checkin_commit(). fsl_checkin_commit() requires a non-stale
     changes scan in order to function properly, but it's a
     computationally slow operation so the checkin process does not
     want to duplicate it if the application has recently done so.
  */
  bool scanForChanges;

  /**
     NOT YET IMPLEMENTED! TODO!

     If true, files which are removed from the SCM by this checkin
     should be removed from the filesystem.

     Reminder to self: when we do this, incorporate
     fsl_rm_empty_dirs().
  */
  bool rmRemovedFiles;

  /**
     Whether to allow (or try to force) a delta manifest or not. 0
     means no deltas allowed - it will generate a baseline
     manifest. Greater than 0 forces generation of a delta if
     possible (if one can be readily found) even if doing so would not
     save a notable amount of space. Less than 0 means to
     decide via some heuristics.

     A "readily available" baseline means either the current checkout
     is a baseline or has a baseline. In either case, we can use that
     as a baseline for a delta. i.e. a baseline "should" generally be
     available except on the initial checkin, which has neither a
     parent checkin nor a baseline.

     The current behaviour for "auto-detect" mode is: it will generate
     a delta if a baseline is "readily available" _and_ the repository
     has at least one delta already. Once it calculates a delta form,
     it calculates whether that form saves any appreciable
     space/overhead compared to whether a baseline manifest was
     generated. If so, it discards the delta and re-generates the
     manifest as a baseline. The "force" behaviour (deltaPolicy>0)
     bypasses the "is it too big?" test, and is only intended for
     testing, not real-life use.

     Caveat: if the repository has the "forbid-delta-manifests" set to
     a true value, this option is ignored: that setting takes
     priority. Similarly, it will not create a delta in a repository
     unless a delta has been "seen" in that repository before or this
     policy is set to >0. When a checkin is created with a delta
     manifest, that fact gets recorded in the repository's config
     table.

     Note that delta manifests have some advantages and may not
     actually save much (if any) repository space because the
     lower-level delta framework already compresses parent versions of
     artifacts tightly. For more information see:

     https://fossil-scm.org/home/doc/tip/www/delta-manifests.md
  */
  int deltaPolicy;

  /**
     Time of the checkin. If 0 or less, the time of the
     fsl_checkin_commit() call is used.
  */
  double julianTime;

  /**
     If this is not NULL then the committed manifest will include a
     tag which closes the branch. The value of this string will be
     the value of the "closed" tag, and the value may be an empty
     string. The intention is that this gets set to a comment about
     why the branch is closed, but it is in no way mandatory.
  */
  char const * closeBranch;

  /**
     Tells fsl_checkin_commit() to dump the generated manifest to
     this file. Intended only for debugging and testing. Checking in
     will fail if this file cannot be opened for writing.
  */
  char const * dumpManifestFile;
  /*
    fossil(1) has many more options. We might want to wrap some of
    it up in the "incremental" state (f->ckin.mf).

    TODOs:

    A callback mechanism which supports the user cancelling
    the checkin. It is (potentially) needed for ops like
    confirming the commit of CRNL-only changes.

    2021-03-09: we now have fsl_confirmer for this but currently no
    part of the checkin code needs a prompt.
  */
};

/**
   Empty-initialized fsl_checkin_opt instance, intended for use in
   const-copy constructing.
*/
#define fsl_checkin_opt_empty_m {               \
  NULL/*message*/,                            \
  NULL/*messageMimeType*/,                  \
  NULL/*user*/,                             \
  NULL/*branch*/,                           \
  NULL/*bgColor*/,                          \
  false/*isPrivate*/,                           \
  true/*calcRCard*/,                           \
  false/*integrate*/,                           \
  false/*allowMergeConflict*/,\
  true/*scanForChanges*/,\
  false/*rmRemovedFiles*/,\
  0/*deltaPolicy*/,                        \
  0.0/*julianTime*/,                        \
  NULL/*closeBranch*/,                      \
  NULL/*dumpManifestFile*/               \
}

/**
   Empty-initialized fsl_checkin_opt instance, intended for use in
   copy-constructing. It is important that clients copy this value
   (or fsl_checkin_opt_empty_m) to cleanly initialize their
   fsl_checkin_opt instances, as this may set default values which
   (e.g.) a memset() would not.
*/
FSL_EXPORT const fsl_checkin_opt fsl_checkin_opt_empty;

/**
   This creates and saves a "checkin manifest" for the current
   checkout.

   Its primary inputs is a list of files to commit. This list is
   provided by the client by calling fsl_checkin_enqueue() one or
   more times.  If no files are explicitely selected (enqueued) then
   it calculates which local files have changed vs the current
   checkout and selects all of those.

   Non-file inputs are provided via the opt parameter.

   On success, it returns 0 and...

   - If newRid is not NULL, it is assigned the new checkin's RID
   value.

   - If newUuid is not NULL, it is assigned the new checkin's UUID
   value. Ownership of the bytes is passed to the caller, who must
   eventually pass them to fsl_free() to free them.

   Note that the new RID and UUID can also be fetched afterwards by
   calling fsl_ckout_version_info().

   On error non-0 is returned and f's error state may (depending on
   the nature of the problem) contain details about the problem.
   Note, however, that any error codes returned here may have arrived
   from several layers down in the internals, and may not have a
   single specific interpretation here. When possible/practical, f's
   error state gets updated with a human-readable description of the
   problem.

   ACHTUNG: all pending checking state is cleaned if this function
   fails for any reason other than basic argument validation. This
   means any queued files or tags need to be re-applied if the client
   wants to try again. That is somewhat of a bummer, but this
   behaviour is the only way we can ensure that then the pending
   checkin state does not get garbled on a second use. When in doubt
   about the state, the client should call fsl_checkin_discard() to
   clear it before try to re-commit. (Potential TODO: add a
   success/fail state flag to the checkin state and only clean up on
   success? OTOH, since something in the state likely caused the
   problem, we might not want to do that.)

   This operation does all of its db-related work in a transaction, so
   it rolls back any db changes if it fails. To implement a "dry-run"
   mode, simply wrap this call in a transaction started on the
   fsl_cx_db_ckout() db handle (passing it to
   fsl_db_transaction_begin()), then, after this call, either cal;
   fsl_db_transaction_rollback() (to implement dry-run mode) or
   fsl_db_transaction_commit() (for "wet-run" mode). If this function
   returns non-0 due to anything more serious than basic argument
   validation, such a transaction will be in a roll-back state.

   Some of the more notable, potentially not obvious, error
   conditions:

   - Trying to commit against a closed leaf: FSL_RC_ACCESS. Doing so
   is not permitted by fossil(1), so we disallow it here.

   - An empty/NULL user name or commit message, or no files were
   selected which actually changed: FSL_RC_MISSING_INFO. In these
   cases f's error state describes the problem.

   - Some resource is not found (e.g. an expected RID/UUID could not
   be resolved): FSL_RC_NOT_FOUND. This would generally indicate
   some sort of data consistency problem. i.e. it's quite possibly
   very bad if this is returned.

   - If the checkin would result in no file-level changes vis-a-vis
   the current checkout, FSL_RC_NOOP is returned.

   BUGS:

   - It cannot currently properly distinguish a "no-op" commit, one in
   which no files were modified or only their permissions were
   modifed.

   @see fsl_checkin_enqueue()
   @see fsl_checkin_dequeue()
   @see fsl_checkin_discard()
   @see fsl_checkin_T_add()
*/
FSL_EXPORT int fsl_checkin_commit(fsl_cx * f, fsl_checkin_opt const * opt,
                                  fsl_id_t * newRid, fsl_uuid_str * newUuid);

/**
   Works like fsl_deck_T_add(), adding the given tag information to
   the pending checkin state. Returns 0 on success, non-0 on error. A
   checkin may, in principal, have any number of tags, and this may be
   called any number of times to add new tags to the pending
   commit. This list of tags gets cleared by a successful
   fsl_checkin_commit() or by fsl_checkin_discard(). Decks require
   that each tag be distinct from each other (none may compare
   equivalent), but that check is delayed until the deck is output
   into its final artifact form.

   @see fsl_checkin_enqueue()
   @see fsl_checkin_dequeue()
   @see fsl_checkin_commit()
   @see fsl_checkin_discard()
   @see fsl_checkin_T_add2()
*/
FSL_EXPORT int fsl_checkin_T_add( fsl_cx * f, fsl_tagtype_e tagType,
                                   fsl_uuid_cstr uuid, char const * name,
                                   char const * value);

/**
   Works identically to fsl_checkin_T_add() except that it takes its
   argument in the form of a T-card object.

   On success ownership of t is passed to mf. On error (see
   fsl_deck_T_add()) ownership is not modified.

   Results are undefined if either argument is NULL or improperly
   initialized.
*/
FSL_EXPORT int fsl_checkin_T_add2( fsl_cx * f, fsl_card_T * t );

/**
   Clears all contents from f's checkout database, including the vfile
   table, vmerge table, and some of the vvar table. The tables are
   left intact. Returns 0 on success, non-0 if f has no checkout or for
   a database error.
 */
FSL_EXPORT int fsl_ckout_clear_db(fsl_cx *f);

/**
   Returns the base name of the current platform's checkout database
   file. That is "_FOSSIL_" on Windows and ".fslckout" everywhere
   else. The returned bytes are static.

   TODO: an API which takes a dir name and looks for either name
*/
FSL_EXPORT char const *fsl_preferred_ckout_db_name();  

/**
   File-overwrite policy values for use with fsl_ckup_opt and friends.
*/
enum fsl_file_overwrite_policy_e {
/** Indicates that an error should be triggered if a file would be
    overwritten. */
FSL_OVERWRITE_ERROR = 0,
/**
   Indicates that files should always be overwritten by 
*/
FSL_OVERWRITE_ALWAYS,
/**
   Indicates that files should never be overwritten, and silently
   skipped over. This is almost never what one wants to do.
*/
FSL_OVERWRITE_NEVER
};
typedef enum fsl_file_overwrite_policy_e fsl_file_overwrite_policy_e;

/**
   State values for use with fsl_ckup_state::fileRmInfo.
*/
enum fsl_ckup_rm_state_e {
/**
   Indicates that the file was not removed in a given checkout.
   Guaranteed to have the value 0 so that it is treated as boolean
   false. No other entries in this enum have well-defined values.
*/
FSL_CKUP_RM_NOT = 0,
/**
   Indicates that a file was removed from a checkout but kept
   in the filesystem because it was locally modified.
*/
FSL_CKUP_RM_KEPT,
/**
   Indicates that a file was removed from a checkout and the
   filesystem, with the caveat that failed attempts to remove from the
   filesystem are ignored for Reasons but will be reported as if the
   unlink worked.
*/
FSL_CKUP_RM
};
typedef enum fsl_ckup_rm_state_e fsl_ckup_rm_state_e;

/**
  Under construction. Work in progress...

  Options for "opening" a fossil repository database. That is,
  creating a new fossil checkout database and populating its schema,
  _without_ checking out any files. (That latter part is up for
  reconsideration and this API might change in the future to check
  out files after creating/opening the db.)
*/
struct fsl_repo_open_ckout_opt {
  /**
     Name of the target directory, which must already exist. May be
     relative, e.g. ".". The repo-open operation will chdir to this
     directory for the duration of the operation. May be NULL, in
     which case the current directory is assumed and no chdir is
     performed.
  */
  char const * targetDir;

  /**
     The filename, with no directory components, of the desired
     checkout db name. For the time being, always leave this NULL and
     let the library decide. It "might" (but probably won't) be
     interesting at some point to allow the client to specify a
     different name (noting that that would be directly incompatible
     with fossil(1)).
  */
  char const * ckoutDbFile;

  /**
     Policy for how to handle overwrites of files extracted from a
     newly-opened checkout.

     Potential TODO: replace this with a fsl_confirmer, though that
     currently seems like overkill for this particular case.
  */
  fsl_file_overwrite_policy_e fileOverwritePolicy;

  /**
     fsl_repo_open_ckout() installs the fossil checkout schema. If
     this is true it will forcibly replace any existing relevant
     schema components in the checkout db, otherwise it will fail when
     it tries to overwrite an existing schema and cannot.
  */
  bool dbOverwritePolicy;

  /**
     Of true, the checkout-open process will look for an opened
     checkout in the target directory and its parents (recursively)
     and fail with FSL_RC_ALREADY_EXISTS if one is found.
  */
  bool checkForOpenedCkout;
};
typedef struct fsl_repo_open_ckout_opt fsl_repo_open_ckout_opt;

/**
  Empty-initialized fsl_repo_open_ckout_opt const-copy constructer.
*/
#define fsl_repo_open_ckout_opt_m { \
  NULL/*targetDir*/, NULL/*ckoutDbFile*/, \
  FSL_OVERWRITE_ERROR/*fileOverwritePolicy*/, \
  false/*dbOverwritePolicy*/,               \
  -1/*checkForOpenedCkout*/              \
}

/**
  Empty-initialised fsl_repo_open_ckout_opt instance. Clients should copy
  this value (or fsl_repo_open_ckout_opt_empty_m) to initialise
  fsl_repo_open_ckout_opt instances for sane default values.
*/
FSL_EXPORT const fsl_repo_open_ckout_opt fsl_repo_open_ckout_opt_empty;

/**
   Work in progress...

   Opens a checkout db for use with the currently-connected repository
   or creates a new one. If opening an existing one, it gets "stolen"
   from any repository it might have been previously mapped to.

   - Requires that f have an opened repository db and no opened
     checkout. Returns FSL_RC_NOT_A_REPO if no repo is opened and
     FSL_RC_MISUSE if a checkout *is* opened.

   - Creates/re-uses a .fslckout DB in the dir opt->targetDir. The
     directory must be NULL or already exist, else FSL_RC_NOT_FOUND is
     returned. If opt->dbOverwritePolicy is false then it fails with
     FSL_RC_ALREADY_EXISTS if that directory already contains a
     checkout db.

   Note that this does not extract any SCM'd files from the
   repository, it only opens (and possibly creates) the checkout
   database.

   Pending:

   - If opening an existing checkout db for a different repo then
   delete the STASH and UNDO entries, as they're not valid for a
   different repo.
*/
FSL_EXPORT int fsl_repo_open_ckout( fsl_cx * f, fsl_repo_open_ckout_opt const * opt );

typedef struct fsl_ckup_state fsl_ckup_state;
/**
   A callback type for use with fsl_ckup_state.  It gets called via
   fsl_repo_ckout() and fsl_ckout_update() to report progress of the
   extraction process. It gets called after one of those functions has
   successfully extracted a file or skipped over it because the file
   existed and the checkout options specified to leave existing files
   in place. It must return 0 on success, and non-0 will end the
   extraction process, propagating that result code back to the
   caller. If this callback fails, the checkout's contents may be left
   in an undefined state, with some files updated and others not.  All
   database-side data will be consistent (the transaction is rolled
   back) but filesystem-side changes may not be.
*/
typedef int (*fsl_ckup_f)( fsl_ckup_state const * cState );

/**
   This enum lists the various types of individual file change states
   which can happen during a checkout or update.
*/
enum fsl_ckup_fchange_e {
/** Sentinel value. */
FSL_CKUP_FCHANGE_INVALID = -1,
/** Was unchanged between the previous and updated-to version,
    so no change was made to the on-disk file. This is the
    only entry in the enum which is guaranteed to have a specific
    value: 0, so that it can be used as a boolean false. */
FSL_CKUP_FCHANGE_NONE = 0,
/**
   Added to SCM in the updated-to version.
*/
FSL_CKUP_FCHANGE_ADDED,
/**
   Added to SCM in the current checkout version and carried over into
   the updated-to version.
*/
FSL_CKUP_FCHANGE_ADD_PROPAGATED,
/** Removed from SCM in the updated-to to version
    OR in the checked-out version but not yet commited.
    a.k.a. it became "unmanaged."


   Do we need to differentiate between those cases?
*/
FSL_CKUP_FCHANGE_RM,
/**
   Removed from the checked-out version but not yet commited,
   so was carried over to the updated-to version.
*/
FSL_CKUP_FCHANGE_RM_PROPAGATED,
/** Updated or replaced without a merge by the checkout/update
    process. */
FSL_CKUP_FCHANGE_UPDATED,
/** Merge was not performed because at least one of the inputs appears
    to be binary. The updated-to version overwrites the previous
    version in this case.
*/
FSL_CKUP_FCHANGE_UPDATED_BINARY,
/** Updated with a merge by the update process. */
FSL_CKUP_FCHANGE_MERGED,
/** Special case of FSL_CKUP_FCHANGE_UPDATED. Merge was performed
    and conflicts were detected. The newly-updated file will contain
    conflict markers.

    @see fsl_buffer_contains_merge_marker()
*/
FSL_CKUP_FCHANGE_CONFLICT_MERGED,
/** Added in the current checkout but also contained in the
    updated-to version. The local copy takes precedence.
*/
FSL_CKUP_FCHANGE_CONFLICT_ADDED,
/**
   Added by the updated-to version but a local unmanaged copy exists.
   The local copy is overwritten, per historical fossil(1) convention
   (noting that fossil has undo support to allow one to avoid loss of
   such a file's contents).

   TODO: use confirmer here to ask user whether to overwrite.
*/
FSL_CKUP_FCHANGE_CONFLICT_ADDED_UNMANAGED,
/** Edited locally but removed from updated-to version. Local
    edits will be left in the checkout tree. */
FSL_CKUP_FCHANGE_CONFLICT_RM,
/** Cannot merge if one or both of the update/updating verions of a
    file is a symlink The updated-to version overwrites the previous
    version in this case.

    We probably need a better name for this.
*/
FSL_CKUP_FCHANGE_CONFLICT_SYMLINK,
/** File was renamed in the updated-to version. If a file is both
    modified and renamed, it is flagged as renamed instead
    of modified. */
FSL_CKUP_FCHANGE_RENAMED,
/** Locally modified. This state appears only when
    "updating" a checkout to the same version. */
FSL_CKUP_FCHANGE_EDITED
};
typedef enum fsl_ckup_fchange_e fsl_ckup_fchange_e;

/**
   State to be passed to fsl_ckup_f() implementations via
   calls to fsl_repo_ckout() and fsl_ckout_update().
*/
struct fsl_ckup_state {
  /**
     The core SCM state for the just-extracted file. Note that its
     content member will be NULL: the content is not passed on via
     this interface because it is only loaded for files which require
     overwriting.

     An update process may synthesize content for extractState->fCard
     which do not 100% reflect the file on disk. Of primary note here:

     1) fCard->uuid will refer to the hash of the updated-to
     version, as opposed to the hash of the on-disk file (which may
     differ due to having local edits merged in). 

     2) For the update process, fCard->priorName will be NULL unless
     the file was renamed between the original and updated-to
     versions, in which case priorName will refer to the original
     version's name.
  */
  fsl_repo_extract_state const * extractState;
  /**
     Optional client-dependent state for use in the
     fsl_ckup_f() callback.
  */
  void * callbackState;

  /**
     Vaguely describes the type of change the current call into
     the fsl_ckup_f() represents. The full range of values is
     not valid for all operations. Specifically:

     Checkout only uses:

     FSL_CKUP_FCHANGE_NONE
     FSL_CKUP_FCHANGE_UPDATED
     FSL_CKUP_FCHANGE_RM

     For update operatios all (or most) values are potentially
     possible.

     If this has a value of FSL_CKUP_FCHANGE_RM,
     this->fileRmInfo will provide a bit more detail.
  */
  fsl_ckup_fchange_e fileChangeType;

  /**
     Indicates whether the file was removed by the process:

     - FSL_CKUP_RM_NOT = Was not removed.

     - FSL_CKUP_RM_KEPT = Was removed from the checked-out version but
     left in the filesystem because the confirmer said to.

     - FSL_CKUP_RM = Was removed from the checkout and the filesystem.

     When this->dryRun is true, this specifies whether the file would
     have been removed.
  */
  fsl_ckup_rm_state_e fileRmInfo;
  
  /**
     If fsl_repo_ckout()'s or fsl_ckout_update()'s options specified
     that the mtime should be set on each updated file, this holds
     that time. If the file existed and was not overwritten, it is set
     to that file's time. Else it is set to the current time (which
     may differ by a small fraction of a second from the file-write
     time because we avoid stat()'ing it again after writing). If
     this->fileRmInfo indicates that a file was removed, this might
     (depending on availability of the file in the filesystem at the
     time) be set to 0.

     When running in dry-run mode, this value may be 0, as we may not
     have a file in place which we can stat() to get it, nor a db
     entry from which to fetch it.
  */
  fsl_time_t mtime;

  /**
     The size of the extracted file, in bytes. If the file was removed
     from the filesystem (or removal was at least attempted) then this
     is set to -1.
  */
  fsl_int_t size;

  /**
     True if the current checkout/update is running in dry-run mode,
     else false. Dry-run mode has 
  */
  bool dryRun;
};

/**
   UNDER CONSTRUCTION.

   Options for use with fsl_repo_ckout() and fsl_ckout_update(). i.e.
   for checkout and update operations.
*/
struct fsl_ckup_opt {
  /**
     The version of the repostitory to check out or update. This must
     be the blob.rid of a checkin artifact.
  */
  fsl_id_t checkinRid;

  /**
     Gets called once per checked-out or updated file, passed a
     fsl_ckup_state instance with information about the
     checked-out file and related metadata. May be NULL.
  */
  fsl_ckup_f callback;

  /**
     State to be passed to this->callback via the
     fsl_ckup_state::callbackState member.
   */
  void * callbackState;

  /**
     An optional "confirmer" for answering questions about file
     overwrites and deletions posed by the checkout process.
     By default this confirmer of the associated fsl_cx instance
     is used.

     Caveats:

     - This is not currently used by the update process, only
     checkout.

     - If this->setMTime is true, the mtime is NOT set for any files
     which already exist and are skipped due to the confirmer saying
     to leave them in place.

     - Similarly, if the confirmer says to never overwrite files,
     permissions on existing files are not modified. fsl_repo_ckout()
     does not (re)write unmodified files, and thus may leave such
     files with different permissions. That's on the to-fix list.
  */
  fsl_confirmer confirmer;
  
  /**
     If true, the checkout/update processes will calculate the
     (synthetic) mtime of each extracted file and set its mtime. This
     is a relatively expensive operation which calculates the
     "effective mtime" of each file by calculating it: Fossil does not
     record file timestamps, instead treating files as if they had the
     timestamp of the most recent checkin in which they were added or
     modified.

     It's generally a good idea to let the update process stamp the
     _current_ time on modified files, in order to avoid any hiccups
     with build processes which rely on accurate times
     (e.g. Makefiles). When doing a clean checkout, it's often
     interesting to see the "original" times, though.
  */
  bool setMtime;

  /**
     A hint to fsl_repo_ckout() and fsl_ckout_update() about whether
     it needs to scan the checkout for changes. Set this to false ONLY
     if the calling code calls fsl_ckout_changes_scan() (or
     equivalent, e.g. fsl_vfile_changes_scan()) immediately before
     calling fsl_repo_ckout() or fsl_ckout_update(), as both require a
     non-stale changes scan in order to function properly.
  */
  bool scanForChanges;

  /**
     If true, the extraction process will "go through the motions" but
     will not write any files to disk. It will perform I/O such as
     stat()'ing to see, e.g., if it would have needed to overwrite a
     file.
  */
  bool dryRun;
};
typedef struct fsl_ckup_opt fsl_ckup_opt;

/**
  Empty-initialized fsl_ckup_opt const-copy constructor.
*/
#define fsl_ckup_opt_m {\
  -1/*checkinRid*/, NULL/*callback*/, NULL/*callbackState*/,  \
  fsl_confirmer_empty_m/*confirmer*/,\
  false/*setMtime*/, true/*scanForChanges*/,false/*dryRun*/ \
}

/**
  Empty-initialised fsl_ckup_opt instance. Clients should copy
  this value (or fsl_ckup_opt_empty_m) to initialise
  fsl_ckup_opt instances for sane default values.
*/
FSL_EXPORT const fsl_ckup_opt fsl_ckup_opt_empty;

/**
   A fsl_repo_extract() proxy which extracts the contents of the
   repository version specified by opt->checkinRid to the root
   directory of f's currently-opened checkout. i.e. it performs a
   "checkout" operation.

   For each extracted entry, cOpt->callback (if not NULL) will be
   passed a (fsl_ckup_state const*) which contains a pointer
   to the fsl_repo_extract_state and some additional metadata
   regarding the extraction. The value of cOpt->callbackState will be
   set as the callbackState member of that fsl_ckup_state
   struct, so that the client has a way of passing around app-specific
   state to that callback.

   After successful completion, the process will report (see below)
   any files which were part of the previous checkout version but are
   not part of the current version, optionally removing them from the
   filesystem (depending on the value of opt->rmMissingPolicy). It
   will IGNORE ANY DELETION FAILURE of files it attempts to
   delete. The reason it does not fail on removal error is because
   doing so would require rolling back the transaction, effectively
   undoing the checkout, but it cannot roll back any prior deletions
   which succeeded. Similarly, after all file removal is complete, it
   attempts to remove any now-empty directories left over by that
   process, also silently ignoring any errors. If the cOpt->dryRun
   option is specified, it will "go through the motions" of removing
   files but will not actually attempt filesystem removal. For
   purposes of the callback, however, it will report deletions as
   having happened (but will also set the dryRun flag on the object
   passed to the callback).

   After unpacking the SCM-side files, it may write out one or more
   manifest files, as described for fsl_ckout_manifest_write(), if the
   'manifest' config setting says to do so.

   As part of the file-removal process, AFTER all "existing" files are
   processed, it calls cOpt->callback() (if not NULL) for each removed
   file, noting the following peculiarities in the
   fsl_ckup_state object which is passed to it for those
   calls:

   - It is called after the processing of "existing" files. Thus the
   file names passed during this step may appear "out of order" with
   regards to the others (which are guaranteed to be passed in lexical
   order, though whether it is case-sensitive or not depends on the
   repository's case-sensitivity setting).

   - fileRmInfo will indicate that the file was removed from the
   checkout, and whether it was actually removed or retained in the
   filesystem. This will indicate filesystem-level removal even when
   in dry-run mode, though in that case no filesystem-level removal is
   actually attempted.

   - extractState->fileRid will refer to the file's blob's RID for the
   previous checkout version.

   - extractState->content will be NULL.

   - extractState->callbackState will be NULL. 

   - extractState->fCard will refer to the pre-removal state of the
   file. i.e. the state as it was in the checkout prior to this
   function being called.

   Returns 0 on success. Returns FSL_RC_NOT_A_REPO if f has no opened
   repo, FSL_RC_NOT_A_CKOUT if no checkout is opened. If
   cOpt->callback is not NULL and returns a non-0 result code,
   extraction ends and that result is returned. If it returns non-0 at
   any point after basic argument validation, it rolls back all
   changes or sets the current transaction stack into a rollback
   state.

   @see fsl_repo_ckout_open()
*/
FSL_EXPORT int fsl_repo_ckout(fsl_cx * f, fsl_ckup_opt const * cOpt);


/**
   UNDER CONSTRUCTION.

   Performs an "update" operation on f's currenly-opened
   checkout. Performing an update is similar to performing a checkout,
   the primary difference being that an update will merge local file
   modifications into any newly-updated files, whereas a checkout will
   overwrite them.

   TODO?: fossil(1)'s update permits a list of files, in which case it
   behaves differently: it updates the given files to the version
   requested but leaves the checkout at its current version. To be
   able to implement that we either need clients to call this in a
   loop, changing opt->filename on each call (like how we do
   fsl_ckout_manage()) or we need a way for them to pass on the list
   of files/dir in the opt object.

   @see fsl_repo_ckout().
*/
FSL_EXPORT int fsl_ckout_update(fsl_cx * f, fsl_ckup_opt const *opt);


/**
   Tries to calculate a version to update the current checkout version
   to, preferring the tip of the current checkout's branch.

   On success, 0 is returned and *outRid is set to the calculated RID,
   which may be 0, indicating that no errors were encountered but no
   version could be calculated.

   On error, non-0 is returned, outRid is not modified, and f's error
   state is updated.

   Returns FSL_RC_NOT_A_CKOUT if f has no checkout opened and
   FSL_RC_NOT_A_REPO if no repo is opened.

   If it calculates that there are multiple viable descendants it
   returns FSL_RC_AMBIGUOUS and f's error state will contain a list of
   the UUIDs (or UUID prefixes) of those descendants.

   Sidebar: to get the absolute latest version, irrespective of the
   branch, use fsl_sym_to_rid() to resolve the symbolic name "tip".
*/
FSL_EXPORT int fsl_ckout_calc_update_version(fsl_cx * f, fsl_id_t * outRid);

/**
   Bitmask used by fsl_ckout_manifest_setting() and
   fsl_ckout_manifest_write().
*/
enum fsl_cx_manifest_mask_e {
FSL_MANIFEST_MAIN = 0x001,
FSL_MANIFEST_UUID = 0x010,
FSL_MANIFEST_TAGS = 0x100
};
typedef enum fsl_cx_manifest_mask_e fsl_cx_manifest_mask_e;

/**
   Returns a bitmask representing which manifest files, if any, will
   be written when opening or updating a checkout directory, as
   specified by the repository's 'manifest' configuration setting, and
   sets *m to a bitmask indicating which of those are enabled. It
   first checks for a versioned setting then, if no versioned setting
   is found, a repository-level setting.

   A truthy setting value (1, "on", "true") means to write the
   manifest and manifest.uuid files. A string with any of the letters
   'r', 'u', or 't' means to write the [r]aw, [u]uid, and/or [t]ags
   file(s), respectively.

   If the manifest setting is falsy or not set, *m is set to 0, else
   *m is set to a bitmask representing which file(s) are considered to
   be auto-generated for this repository:

   - FSL_MANIFEST_MAIN = manifest
   - FSL_MANIFEST_UUID = manifest.uuid
   - FSL_MANIFEST_TAGS = manifest.tags

   Any db-related or allocation errors while trying to fetch the
   setting are silently ignored.

   For performance's sake, since this is potentially called often from
   fsl_reserved_fn_check(), this setting is currently cached by this
   routine (in the fsl_cx object), but that ignores the fact that the
   manifest setting can be modified at any time, either in a versioned
   setting file or the repository db, and may be modified from outside
   the library. There's a tiny back-door for working around that: if m
   is NULL, the cache will be flushed and no other work will be
   performed. Thus the following approach can be used to force a fresh
   check for that setting:

   @code
   fsl_ckout_manifest_setting(f, NULL); // clears caches, does nothing else
   fsl_ckout_manifest_setting(f, &myInt); // loads/caches the setting
   @endcode
*/
FSL_EXPORT void fsl_ckout_manifest_setting(fsl_cx *f, int *m);

/**
   Might write out the files manifest, manifest.uuid, and/or
   manifest.tags for the current checkout to the the checkout's root
   directory. The 2nd-4th arguments are interpreted as follows:

   0: Do not write that file.

   >0: Always write that file.

   <0: Use the value of the "manifest" config setting (see
   fsl_ckout_manifest_setting()) to determine whether or not to write
   that file.

   As each file is written, its mtime is set to that of the checkout
   version. (Forewarning: that behaviour may change if it proves to be
   problematic vis a vis build processes.)

   Returns 0 on success, non-0 on error:

   - FSL_RC_NOT_A_CKOUT if no checkout is opened.

   - FSL_RC_RANGE if the current checkout RID is 0 (indicating a fresh,
   empty repository).

   - Various potential DB/IO-related error codes.

   If the final argument is not NULL then it will be updated to
   contain a bitmask representing which files, if any, were written:
   see fsl_ckout_manifest_setting() for the values. It is updated
   regardless of success or failure and will indicate which file(s)
   was/were written before the error was triggered.

   Each file implied by the various manifest settings which is NOT
   written by this routine and is also not part of the current
   checkout (i.e. not listed in the vfile table) will be removed from
   disk, but a failure while attempting to do so will be silently
   ignored.
*/
FSL_EXPORT int fsl_ckout_manifest_write(fsl_cx *f,
                                        int manifest,
                                        int manifestUuid,
                                        int manifestTags,
                                        int *wroteWhat );

/**
   Returns true if f has an opened checkout and the given absolute
   path is rooted in that checkout, else false. As a special case, it
   returns false if the path _is_ the checkout root unless zAbsPath
   has a trailing slash. (The checkout root is always stored with a
   trailing slash because that simplifies its internal usage.)

   Note that this is strictly a string comparison, not a
   filesystem-level operation.
*/
FSL_EXPORT bool fsl_is_rooted_in_ckout(fsl_cx *f, char const *zAbsPath);

/**
   Works like fsl_is_rooted_in_ckout() except that it returns 0 on
   success, and on error updates f with a description of the problem
   and returns non-0: FSL_RC_RANGE or (if updating the error state
   fails) FSL_RC_OOM.
 */
FSL_EXPORT int fsl_is_rooted_in_ckout2(fsl_cx *f, char const *zAbsPath);

/**
   Change-type values for use with fsl_ckout_revert_f() callbacks.
*/
enum fsl_ckout_revert_e {
/** Sentinel value. */
FSL_REVERT_NONE = 0,
/**
   File was previously queued for addition but unqueued
   by the revert process.
*/
FSL_REVERT_UNMANAGE,
/**
   File was previously queued for removal but unqueued by the revert
   process. If the file's contents or permissions were also reverted
   then the file is reported as FSL_REVERT_PERMISSIONS or
   FSL_REVERT_CONTENTS instead.
*/
FSL_REVERT_REMOVE,
/**
   File was previously scheduled to be renamed, but the rename was
   reverted. The name reported to the callback is the original one.
   If a file was both modified and renamed, it will be flagged as
   renamed instead of modified, for consistency with the usage of
   fsl_ckup_fchange_e's FSL_CKUP_FCHANGE_RENAMED.

   FIXME: this does not mean that the file on disk was actually
   renamed (if needed). That is TODO, pending addition of code to
   perform renames.
*/
FSL_REVERT_RENAME,
/** File's permissions (only) were reverted. */
FSL_REVERT_PERMISSIONS,
/**
   File's contents reverted. This value trumps any others in this
   enum. Thus if a file's permissions and contents were reverted,
   or it was un-renamed and its contents reverted, it will be
   reported using this enum entry.
*/
FSL_REVERT_CONTENTS
};
typedef enum fsl_ckout_revert_e fsl_ckout_revert_e;
/**
   Callback type for use with fsl_ckout_revert(). For each reverted
   file it gets passed the checkout-relative filename, type of change,
   and the callback state pointer which was passed to
   fsl_ckout_revert(). If it returns non-0, the revert process will
   end in an error and that code will be propagated back to the
   caller.  In such cases, any files reverted up until that point will
   still be reverted on disk but the reversion in the database will be
   rolled back. A change scan (e.g. fsl_ckout_changes_scan()) will
   restore balance to that equation, but these callbacks should only
   return non-0 in for catastrophic failure.
*/
typedef int (*fsl_ckout_revert_f)( char const *zFilename,
                                   fsl_ckout_revert_e changeType,
                                   void * callbackState );

/**
   Options for passing to fsl_ckout_revert().
*/
struct fsl_ckout_revert_opt {
  /**
     File or directory name to revert. See also this->vfileIds.
  */
  char const * filename;
  /**
     An alternative to assigning this->filename is to point
     this->vfileIds to a bag of vfile.id values. If this member is not
     NULL, fsl_ckout_revert() will ignore this->filename.

     @see fsl_filename_to_vfile_ids()
  */
  fsl_id_bag const * vfileIds;
  /**
     Interpret filename as relative to cwd if true, else relative to
     the current checkout root. This is ignored when this->vfileIds is
     not NULL.
  */
  bool relativeToCwd;
  /**
     If true, fsl_vfile_changes_scan() is called to ensure that
     the filesystem and vfile tables agree. If the client code has
     called that function, or its equivalent, since any changes were
     made to the checkout then this may be set to false to speed up
     the revert process.
  */
  bool scanForChanges;
  /**
     Optional callback to notify the client of what gets reverted.
  */
  fsl_ckout_revert_f callback;
  /**
     State for this->callback.
  */
  void * callbackState;
};
typedef struct fsl_ckout_revert_opt fsl_ckout_revert_opt;
/**
   Initialized-with-defaults fsl_ckout_revert_opt instance,
   intended for use in const-copy initialization.
*/
#define fsl_ckout_revert_opt_empty_m { \
  NULL/*filename*/,NULL/*vfileIds*/,true/*relativeToCwd*/,true/*scanForChanges*/, \
  NULL/*callback*/,NULL/*callbackState*/      \
}
/**
   Initialized-with-defaults fsl_ckout_revert_opt instance,
   intended for use in non-const copy initialization.
*/
FSL_EXPORT const fsl_ckout_revert_opt fsl_ckout_revert_opt_empty;

/**
   Reverts changes to checked-out files, replacing their
   on-disk versions with the current checkout's version.

   If zFilename refers to a directory, all managed files under that
   directory are reverted (if modified). If zFilename is NULL or
   empty, all modifications in the current checkout are reverted.

   If a file has been added but not yet committed, the add
   is un-queued but the file is otherwise untouched. If the
   file has been queued for removal, this removes it from
   that queue as well as restores its contents.

   If a rename is pending for the given filename, the name may match
   either its original name or new name. Whether or not that will
   actually work when file A is renamed to B and file C is renamed to
   A is anyone's guess. (Noting that (fossil mv) won't allow that
   situation to exist in the vfile table for a single checkout
   version, so it seems safe enough.)

   If the 4th argument is not NULL then it is called for each revert
   (_after_ the revert happens) to report what was done with that
   file. It gets passed the checkout-relative name of each reverted
   file. The 5th argument is not interpreted by this function but is
   passed on as-is as the final argument to the callback. If the
   callback returns non-0, the revert process is cancelled, any
   pending transaction is set to roll back, and that error code is
   returned. Note that cancelling a revert mid-process will leave file
   changes made by the revert so far in place, and thus the checkout
   db and filesystem will be in an inconsistent state until
   fsl_vfile_changes_scan() (or equivalent) is called to restore
   balance to the world.

   Files which are not actually reverted because their contents or
   permissions were not modified on disk are not reported to the
   callback unless the reversion was the un-queuing of an ADD or
   REMOVE operation.

   Returns 0 on success, any number of non-0 results on error.
*/
FSL_EXPORT int fsl_ckout_revert( fsl_cx * f,
                                 fsl_ckout_revert_opt const * opt );

/**
   Expects f to have an opened checkout and zName to be the name of
   an entry in the vfile table where vfile.vid == vid. If vid<=0 then
   the current checkout RID is used. This function does not do any
   path resolution or normalization on zName and checks only for an
   exact match (honoring f's case-sensitivity setting - see
   fsl_cx_case_sensitive_set()).

   On success it returns 0 and assigns *vfid to the vfile.id value of
   the matching file.  If no match is found, 0 is returned and *vfile
   is set to 0. Returns FSL_RC_NOT_A_CKOUT if no checkout is opened,
   FSL_RC_RANGE if zName is not a simple path (see
   fsl_is_simple_pathname()), and any number of codes for db-related
   errors.

   This function matches only vfile.pathname, not vfile.origname,
   because it is possible for a given name to be in both fields (in
   different records) at the same time.
*/
FSL_EXPORT int fsl_filename_to_vfile_id( fsl_cx * f, fsl_id_t vid,
                                         char const * zName,
                                         fsl_id_t * vfid );

/**
   Searches the vfile table where vfile.vid=vid for a name which
   matches zName or all vfile entries found under a subdirectory named
   zName (with no trailing slash). zName must be relative to the
   checkout root. As a special case, if zName is NULL, empty, or "."
   then all files in vfile with the given vid are selected. For each
   entry it finds, it adds the vfile.id to dest. If vid<=0 then the
   current checkout RID is used.

   If changedOnly is true then only entries which have been marked in
   the vfile table as having some sort of change are included, so if
   true then fsl_ckout_changes_scan() (or equivalent) must have been
   "recently" called to ensure that state is up to do.

   This search honors the context-level case-sensitivity 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. In order to determine whether or
   not any results were found, compare dest->entryCount before and
   after calling this.

   This function matches only vfile.pathname, not vfile.origname,
   because it is possible for a given name to be in both fields (in
   different records) at the same time.

   @see fsl_ckout_vfile_ids()
*/
FSL_EXPORT int fsl_filename_to_vfile_ids( fsl_cx * f, fsl_id_t vid,
                                          fsl_id_bag * dest,
                                          char const * zName,
                                          bool changedOnly);

/**
   This is a variant of fsl_filename_to_vfile_ids() which accepts
   filenames in a more flexible form than that routine. This routine
   works exactly like that one except for the following differences:

   1) The given filename and the relativeToCwd arguments are passed to
   by fsl_ckout_filename_check() to canonicalize the name and ensure
   that it points to someplace within f's current checkout.

   2) Because of (1), zName may not be NULL or empty. To fetch all of
   the vfile IDs for the current checkout, pass a zName of "."  and
   relativeToCwd=false.

   Returns 0 on success, FSL_RC_MISUSE if zName is NULL or empty,
   FSL_RC_OOM on allocation error, FSL_RC_NOT_A_CKOUT if f has no
   opened checkout.
*/
FSL_EXPORT int fsl_ckout_vfile_ids( fsl_cx * f, fsl_id_t vid,
                                    fsl_id_bag * dest, char const * zName,
                                    bool relativeToCwd, bool changedOnly );


/**
   This "mostly internal" routine (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 (and several processes rely on that behavior to
   keep it from nuking, e.g., as-yet-uncommitted queued add/rm
   entries).

   Returns 0 on success, any number of codes on any of many potential
   errors.

   f must not be NULL and must have opened checkout and repository
   databases. In debug builds it will assert that that is so.

   If the 3rd argument is true, any entries in vfile for checkin
   versions other than the one specified in the 2nd argument are
   cleared from the vfile table. That is _almost_ always the desired
   behavior, but there are rare cases where vfile needs to temporarily
   (for the duration of a single transaction) hold state for multiple
   versions.

   If the 4th argument is not NULL, it gets assigned the number of
   blobs from the given version which are currently missing from the
   repository due to being phantoms (as opposed to being shunned).

   Returns 0 on success, FSL_RC_NOT_A_CKOUT if no checkout is opened,
   FSL_RC_OOM on allocation error, FSL_RC_DB for db-related problems,
   et.al.

   Misc. notes:

   - This does NOT update the "checkout" vvar table entry because this
   routine is sometimes used in contexts where we need to briefly
   maintain two vfile versions and keep the previous checkout version.

   - Apps must take care to not leave more than one version in the
   vfile table for longer than absolutely needed. They "really should"
   use fsl_vfile_unload() to clear out any version they load with this
   routine.

   @see fsl_vfile_unload()
   @see fsl_vfile_unload_except()
*/
FSL_EXPORT int fsl_vfile_load(fsl_cx * f, fsl_id_t manifestRid,
                              bool clearOtherVersions,
                              uint32_t * missingCount);

/**
   Clears out all entries in the current checkout's vfile table with
   the given vfile.vid value. If vid<=0 then the current checkout RID
   is used (which is never a good idea from client-side code!).

   ACHTUNG: never do this without understanding the consequences. It
   can ruin the current checkout state.

   Returns 0 on success, FSL_RC_NOT_A_CKOUT if f has no checkout
   opened, FSL_RC_DB on any sort of db-related error (in which case
   f's error state is updated with a description of the problem).

   @see fsl_vfile_load()
   @see fsl_vfile_unload_except()
*/
FSL_EXPORT int fsl_vfile_unload(fsl_cx * f, fsl_id_t vid);

/**
   A counterpart of fsl_vfile_unload() which removes all vfile
   entries where vfile.vid is not the given vid. If vid is <=0 then
   the current checkout RID is used.

   Returns 0 on success, FSL_RC_NOT_A_CKOUT if f has no checkout
   opened, FSL_RC_DB on any sort of db-related error (in which case
   f's error state is updated with a description of the problem).

   @see fsl_vfile_load()
   @see fsl_vfile_unload()
*/
FSL_EXPORT int fsl_vfile_unload_except(fsl_cx * f, fsl_id_t vid);


/**
   Performs a "fingerprint check" between f's current checkout and
   repository databases. Returns 0 if either there is no mismatch or
   it is impossible to determine because the checkout is missing a
   fingerprint (which is legal for "older" checkout databases).

   If a mismatch is found, FSL_RC_REPO_MISMATCH is returned. Returns
   some other non-0 code on a lower-level error (db access, OOM,
   etc.).

   A mismatch can happen when the repository to which a checkout
   belongs is replaced, either with a completely different repository
   or a copy/clone of that same repository. Each repository copy may
   have differing blob.rid values, and those are what the checkout
   database uses to refer to repository-side data. If those RIDs
   change, then the checkout is left pointing to data other than what
   it should be.

   TODO: currently the library offers no automated recovery mechanism
   from a mismatch, the only remedy being to close the checkout
   database, destroy it, and re-create it. fossil(1) is able, in some cases,
   to automatically recover from this situation.
*/
FSL_EXPORT int fsl_ckout_fingerprint_check(fsl_cx * f);

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


































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted include/fossil-scm/fossil-checkout.h.orig.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
/* -*- 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_CHECKOUT_H_INCLUDED)
#define NET_FOSSIL_SCM_FSL_CHECKOUT_H_INCLUDED
/*
  Copyright 2013-2021 Stephan Beal (https://wanderinghorse.net).

  Derived heavily from previous work:

  Copyright (c) 2013 D. Richard Hipp (https://www.hwaci.com/drh/)


  This program is free software; you can redistribute it and/or
  modify it under the terms of the Simplified BSD License (also
  known as the "2-Clause License" or "FreeBSD License".)

  This program is distributed in the hope that it will be useful,
  but without any warranty; without even the implied warranty of
  merchantability or fitness for a particular purpose.

  ******************************************************************************
  This file declares public APIs for working with checkout-side fossil content.
*/

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

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


/**
   Returns version information for the current checkout.

   If f is not NULL and has an opened checkout then...

   If uuid is not NULL then *uuid is set to the UUID of the opened
   checkout. If rid is not NULL, *rid is set to the record ID of
   that checkout. The returned uuid bytes and rid are valid until
   the library closes the checkout db or updates its state to a
   newer checkout version. When in doubt about lifetime issues,
   copy the UUID immediately after calling this if they will be
   needed later.

   Corner case: a new repo with no checkins has an RID of 0
   and a UUID of NULL.

   If f is NULL or has no checkout then *uuid will be set to NULL
   and *rid will be set to 0.
*/
FSL_EXPORT void fsl_checkout_version_info(fsl_cx *f, fsl_id_t * rid,
                                          fsl_uuid_cstr * uuid );

/**
   Given a fsl_cx with an opened checkout, and a filename, this
   function canonicalizes zOrigName to a form suitable for use as
   an in-repo filename, _appending_ the results to pOut. If pOut is
   NULL, it performs its normal checking but does not write a
   result, other than to return 0 for success.

   As a special case, if zOrigName refers to the top-level checkout
   directory, it resolves to either "." or "./", depending on whether
   zOrigName contains a trailing slash.

   If relativeToCwd is true then the filename is canonicalized
   based on the current working directory (see fsl_getcwd()),
   otherwise f's current checkout directory is used as the virtual
   root.

   If the input name contains a trailing slash, it is retained in
   the output sent to pOut except in the top-dir case mentioned
   above.

   Returns 0 on success, meaning that the value appended to pOut
   (if not NULL) is a syntactically valid checkout-relative path.

   Returns FSL_RC_RANGE if zOrigName points to a path outside
   of f's current checkout root.

   Returns FSL_RC_NOT_A_CHECKOUT if f has no checkout opened.

   Returns FSL_RC_MISUSE if !f, !zOrigName, FSL_RC_OOM on an
   allocation error.

   This function does not validate whether or not the file actually
   exists, only that its name is potentially valid as a filename
   for use in a checkout (though other, downstream rules might prohibit that, e.g.
   the filename "..../...." is not valid but is not seen as invalid by
   this function). (Reminder to self: we could run the end result through
   fsl_is_simple_pathname() to catch that?)
*/
FSL_EXPORT int fsl_checkout_filename_check( fsl_cx * f, char relativeToCwd,
                                            char const * zOrigName, fsl_buffer * pOut );


/**
   Adds the given filename to the current checkout vfile list of
   files as a to-be-added file, or updates an existing record if
   one exists.

   If relativeToCwd is true (non-0) then the filename is
   resolved/canonicalized based on the current working directory
   (see fsl_getcwd()), otherwise f's current checkout directory is
   used as the virtual root. This makes a subtle yet important
   difference in how the name is resolved. CLI apps which take file
   names from the user will generally want to set relativeToCwd to
   true. GUI apps, OTOH, will possibly need it to be false,
   depending on how they resolve and pass on the filenames.

   This function ensures that zFilename gets canonicalized and can
   be found under the checkout directory, and fails if no such file
   exists (checking against the canonicalized name).

   Returns 0 on success, non-0 on error.

   Note that unlike fsl_checkout_file_rm(), this routine cannot
   recursively add files from a directory name. Fixing that is on
   the TODO list.

   @see fsl_checkout_file_rm()
*/
FSL_EXPORT int fsl_checkout_file_add( fsl_cx * f, char relativeToCwd, char const * zFilename );

/**
   The converse of fsl_checkout_file_add(), this queues a file for
   removal from the current checkout. The arguments have identical
   meanings as for fsl_checkout_file_add() except that this routine
   does not ensure that the resolved filename actually exists - it
   only normalizes zFilename into its repository-friendly form.

   If recurseDirs is true then if zFilename refers to a directory
   then this operation queues all files under that directory
   (recursively) for removal. In this case, it is irrelevant
   whether or not zFilename ends in a trailing slash or not.

   Returns 0 on success, any of a number of non-0 codes on error.
   Returns FSL_RC_MISUSE if !f, !zFilename, or !*zFilename.
   Returns FSL_RC_NOT_A_CHECKOUT if f has no opened checkout.

   @see fsl_checkout_file_add()
*/
FSL_EXPORT int fsl_checkout_file_rm( fsl_cx * f, char relativeToCwd, char const * zFilename,
                          char recurseDirs );

/**
   Change-type flags for use with fsl_checkout_changes_visit() and
   friends.
*/
enum fsl_checkout_change_t {
/**
   Sentinel placeholder value.
*/
FSL_CKOUT_CHANGE_NONE = 0,
/**
   Indicates that a file was modified in some unspecified way.
*/
FSL_CKOUT_CHANGE_MOD,
/**
   Indicates that a file was modified as the result of a merge.
*/
FSL_CKOUT_CHANGE_MERGE_MOD,
/**
   Indicates that a file was added as the result of a merge.
*/
FSL_CKOUT_CHANGE_MERGE_ADD,
/**
   Indicates that a file was modified as the result of an
   integrate-merge.
*/
FSL_CKOUT_CHANGE_INTEGRATE_MOD,
/**
   Indicates that a file was added as the result of an
   integrate-merge.
*/
FSL_CKOUT_CHANGE_INTEGRATE_ADD,
/**
   Indicates that a file was added.
*/
FSL_CKOUT_CHANGE_ADDED,
/**
   Indicates that a file was removed.
*/
FSL_CKOUT_CHANGE_REMOVED,
/**
   Indicates that a file is missing from the local checkout.
*/
FSL_CKOUT_CHANGE_MISSING,
/**
   Indicates that a file was renamed.
*/
FSL_CKOUT_CHANGE_RENAMED,
/**
   NOT YET USED.

   Indicates that a file contains conflict markers.
*/
FSL_CKOUT_CHANGE_CONFLICT,
/**
   NOT YET USED.

   Indicates that a file in the changes table references
   a non-file on disk.
*/
FSL_CKOUT_CHANGE_NOT_A_FILE,
/**
   NOT YET USED.

   Indicates that a file is part of a cherrypick merge.
*/
FSL_CKOUT_CHANGE_CHERRYPICK,
/**
   NOT YET USED.

   Indicates that a file is part of a backout.
*/
FSL_CKOUT_CHANGE_BACKOUT
};

typedef enum fsl_checkout_change_t fsl_checkout_change_t;

/**
   Sets up the vfile table in f's opened checkout db and scans the
   checkout root directory's contents for changes compared to the
   pristine checkout state. It records any changes in the vfile
   table.

   Returns 0 on success, non-0 on error, FSL_RC_MISUSE
   if !f.

   For compatibility with fossil(1), this routine clears the vfile
   table of any entries not related to the current checkout.

   TODO: a variant of this which scans only a given file or directory.

   @see fsl_checkout_changes_visit()
*/
FSL_EXPORT int fsl_checkout_changes_scan(fsl_cx * f);

/**
   A typedef for visitors of checkout status information via
   fsl_checkout_changes_visit(). Implementions will receive the
   last argument passed to fsl_checkout_changes_visit() as their
   first argument. The second argument indicates the type of change
   and the third holds the repository-relative name of the file.

   If changes is FSL_CKOUT_CHANGE_RENAMED then origName will hold
   the original name, else it will be NULL.

   Implementations must return 0 on success, non-zero on error. On
   error any looping performed by fsl_checkout_changes_visit() will
   stop and this function's result code will be returned.

   @see fsl_checkout_changes_visit()
*/
typedef int (*fsl_checkout_changes_f)(void * state, fsl_checkout_change_t change,
                                      char const * filename,
                                      char const * origName);

/**
   Compares the changes of f's local checkout against repository
   version vid (checkout version if vid is negative). For each
   change detected it calls visitor(state,...) to report the
   change.  If visitor() returns non-0, that code is returned from
   this function. If doChangeScan is true then
   fsl_checkout_changes_scan() is called by this function before
   iterating, otherwise it is assumed that the caller has called
   that or has otherwise ensured that the checkout db's vfile table
   has been populated.

   Returns 0 on success.

   @see fsl_checkout_changes_scan()
*/
FSL_EXPORT int fsl_checkout_changes_visit( fsl_cx * f, fsl_id_t vid,
                                           char doChangeScan,
                                           fsl_checkout_changes_f visitor,
                                           void * state );
/**
   A bitmask of flags for fsl_vfile_changes_scan().
*/
enum fsl_ckout_sig_t {
/**
   The empty flags set.
*/
FSL_VFILE_CKSIG_NONE = 0,

/**
   Non-file FS objects throw an error. Not yet implemented.
*/
FSL_VFILE_CKSIG_ENOTFILE = 0x001,
/**
   Verify file content using sha1sum, regardless of whether or not
   file timestamps differ.
*/
FSL_VFILE_CKSIG_SHA1 = 0x002,
/**
   For unchanged or changed-by-merge files, set the mtime to last
   check-out time, as determined by fsl_mtime_of_manifest_file().
*/
FSL_VFILE_CKSIG_SETMTIME = 0x004,
/**
   Indicates that when populating the vfile table, it should be
   cleared of entries for other checkins. This is primarily for
   compatibility with fossil(1), which generally assumes only a
   single checkin's worth of state is in vfile and can get confused
   if that is not the case.
*/
FSL_VFILE_CKSIG_CLEAR_VFILE = 0x008
};

/**
    This function populates (if needed) the vfile table of f's
    checkout db for the given checkin version ID then compares files
    listed in it against files in the checkout directory, updating
    vfile's status for the current checkout version id as its goes.
    If vid is negative then the current checkout's RID is used in
    its place (note that 0 is the RID of an initial empty
    repository!). cksigFlags must be a bitmask of fsl_ckout_sig_t
    values.

    Returns 0 on success, non-0 on error.

    BUG: this does not properly catch one particular corner-case
    change, where a file has been replaced by a same-named non-file
    (symlink or directory).
*/
FSL_EXPORT int fsl_vfile_changes_scan(fsl_cx * f, fsl_id_t vid, int cksigFlags);


/**
   Adds the given file f's list of "selected" files - the list of
   filenames which should be included in the next commit
   (see fsl_checkin_commit()).

   Warning: if this function is not called before
   fsl_checkin_commit(), then fsl_checkin_commit() will select all
   modified, added, removed, or renamed files by default.

   zName must be a non-empty NUL-terminated string and its bytes are
   copied. The filename is canonicalized via
   fsl_checkout_filename_check() - see that function for the meaning
   of the relativeToCwd parameter.

   The resolved name must refer to either a single vfile.pathname
   value in the current vfile table (i.e. in the current checkout,
   though possibly newly-added and not necessarily committed), or it
   must refer to a directory, under which all modified, added,
   deleted, or renamed files are queued up for the next commit.

   If given a single file name it returns FSL_RC_NOT_FOUND if the
   file is not in the current checkout. In this case f's error state
   is updated with a description of the problem.

   If given a directory name, it does not validate that any changes
   are actually detected for queuing up (that detection comes at the
   final commit stage).

   The change-state of the file(s) is not actually checked by this
   function, other than to confirm that the file is indeed listed in
   the current checkout. That means that the file may still be
   modified by the client before the commit takes place, and the
   changes on disk at the point of the fsl_checkin_commit() are the
   ones which get saved (or not).

   Returns 0 on success, FSL_RC_MISUSE if either pointer is NULL,
   or *zName is NUL. Returns FSL_RC_OOM on allocation error.

   On error f's error state might (depending on the nature of the
   problem) contain more details.

   Returns 0 and has no side-effects if zName is already in the
   checkin queue. This function honors the
   fsl_cx_is_case_sensitive() setting when comparing names but the
   check for the repo-level file is case-sensitive! That's arguably
   a bug.

   @see fsl_checkin_file_is_enqueued()
   @see fsl_checkin_file_dequeue()
   @see fsl_checkin_discard()
   @see fsl_checkin_commit()
*/
FSL_EXPORT int fsl_checkin_file_enqueue(fsl_cx * f, char const * zName,
                             char relativeToCwd);

/**
   The opposite of fsl_checkin_file_enqueue(), then removes the
   given file or directory name from f's checkin queue. Returns 0 on
   succes. Unlike fsl_checkin_file_enqueue(), this function does
   little validation on the input and simply asks the internals to
   clean up. Specifically, it does not return an error if this
   operation finds no entries to unqueue. If zName is empty or NULL
   then ALL files are unqueued from the pending checkin.

   If relativeToCwd is true (non-0) then zName is resolved based on
   the current directory, otherwise it is resolved based on the
   checkout's root directory.


   @see fsl_checkin_file_enqueue()
   @see fsl_checkin_file_is_enqueued()
   @see fsl_checkin_discard()
   @see fsl_checkin_commit()
*/
FSL_EXPORT int fsl_checkin_file_dequeue(fsl_cx * f, char const * zName,
                             char relativeToCwd);

/**
   Returns true (non-0) if the file named by zName is in f's current
   file checkin queue.  If NO files are in the current selection
   queue then this routine assumes that ALL files are implicitely
   selected. As long as at least once file is enqueud (via
   fsl_checkin_file_enqueue()) then this function only returns true
   for files which have been explicitly enqueued.

   If relativeToCwd then zName is resolved based on the current
   directory, otherwise it is resolved based on the checkout's
   root directory.

   This function returning true does not necessarily indicate that
   the file _will_ be checked in at the next commit. If the file has
   not been modified at commit-time then it will not be part of the
   commit.

   This function honors the fsl_cx_is_case_sensitive() setting
   when comparing names.

   Achtung: this does not resolve directory names like
   fsl_checkin_file_enqueue() and fsl_checkin_file_dequeue() do. It
   only works with file names.

   @see fsl_checkin_file_enqueue()
   @see fsl_checkin_file_dequeue()
   @see fsl_checkin_discard()
   @see fsl_checkin_commit()
*/
FSL_EXPORT bool fsl_checkin_file_is_enqueued(fsl_cx * f, char const * zName,
                                             int relativeToCwd);

/**
   Discards any state accumulated for a pending checking,
   including any files queued via fsl_checkin_file_enqueue()
   and tags added via fsl_checkin_T_add().

   @see fsl_checkin_file_enqueue()
   @see fsl_checkin_file_dequeue()
   @see fsl_checkin_file_is_enqueued()
   @see fsl_checkin_commit()
   @see fsl_checkin_T_add()
*/
FSL_EXPORT void fsl_checkin_discard(fsl_cx * f);

/**
   Parameters for fsl_checkin_commit().

   Checkins are created in a multi-step process:

   - fsl_checkin_file_enqueue() queues up a file or directory for
   commit at the next commit.

   - fsl_checkin_file_dequeue() removes an entry, allowing
   UIs to toggle files in and out of a checkin before
   committing it.

   - fsl_checkin_file_is_enqueued() can be used to determine whether
   a given name is already enqueued or not.

   - fsl_checkin_T_add() can be used to T-cards (tags) to a
   deck. Branch tags are intended to be applied via the
   fsl_checkin_opt::branch member.

   - fsl_checkin_discard() can be used to cancel any pending file
   enqueuings, effectively cancelling a commit (which can be
   re-started by enqueuing another file).

   - fsl_checkin_commit() creates a checkin for the list of enqueued
   files (defaulting to all modified files in the checkout!). It
   takes an object of this type to specify a variety of parameters
   for the check.

   Note that this API uses the terms "enqueue" and "unqueue" rather
   than "add" and "remove" because those both have very specific
   (and much different) meanings in the overall SCM scheme.
*/
struct fsl_checkin_opt {
  /**
     The commit message. May not be empty - the library
     forbids empty checkin messages.
  */
  char const * message;

  /**
     The optional mime type for the message. Only set
     this if you know what you're doing.
  */
  char const * messageMimeType;

  /**
     The user name for the checkin. If NULL or empty, it defaults to
     fsl_cx_user_get(). If that is NULL, a FSL_RC_RANGE error is
     triggered.
  */
  char const * user;

  /**
     Don't use this yet - it is not yet tested all that well.

     If not NULL, makes the checkin the start of a new branch with
     this name.
  */
  char const * branch;

  /**
     If this->branch is not NULL, this is applied as its "bgcolor"
     propagating property. If this->branch is NULL then this is
     applied as a one-time color tag to the checkin.

     It must be NULL, empty, or in a form usable by HTML/CSS,
     preferably \#RRGGBB form. Length-0 values are ignored (as if
     they were NULL).
  */
  char const * bgColor;

  /**
     If true, the checkin will be marked as private, otherwise it
     will be marked as private or public, depending on whether or
     not it inherits private content.
  */
  bool isPrivate;

  /**
     Whether or not to calculate an R-card. Doing so is very
     expensive (memory and I/O) but it adds another layer of
     consistency checking to manifest files. In practice, the R-card
     is somewhat superfluous and the cost of calculating it has
     proven painful on very large repositories. fossil(1) creates an
     R-card for all checkins but does not require that one be set
     when it reads a manifest.
  */
  bool calcRCard;

  /**
     Whether to allow (or try to force) a delta manifest or not. 0
     means no deltas allowed - it will generate a baseline
     manifest. Greater than 0 forces generation of a delta if
     possible (if one can be readily found) even if doing so would not
     save a notable amount of space. Less than 0 means to
     decide via some heuristics.

     A "readily available" baseline means either the current
     checkout is a baseline or has a baseline. In either case, we
     can use that as a baseline for a delta. i.e. a baseline
     "should" basically available except on the initial checkin,
     which has neither a parent checkin nor a baseline.

     The current behaviour for "auto-detect" mode is: it will generate
     a delta if a baseline is "readily available." Once it calculates
     a delta form, it calculates whether that form saves any
     appreciable space/overhead compared to whether a baseline
     manifest was generated. If so, it discards the delta and
     re-generates the manifest as a baseline. The "force" behaviour
     (deltaPolicy>0) bypasses the "is it too big?" test, and is only
     intended for testing, not real-life use.

     Caveat: if the repository has the "forbid-delta-manifests" set to
     a true value, this option is ignored: that setting takes
     priority.
  */
  int deltaPolicy;

  /**
     Tells the checkin to close merged-in branches (merge type of
     0). INTEGRATE merges (type=-4) are always closed by a
     checkin. This does not apply to CHERRYPICK (type=-1) and
     BACKOUT (type=-2) merges.
  */
  bool integrate;

  /**
     Time of the checkin. If 0 or less, the current time
     is used.
  */
  double julianTime;

  /**
     If this is not NULL then the committed manifest will include a
     tag which closes the branch. The value of this string will be
     the value of the "closed" tag, and the value may be an empty
     string. The intention is that this gets set to a comment about
     why the branch is closed, but it is in no way mandatory.
  */
  char const * closeBranch;

  /**
     Tells fsl_checkin_commit() to dump the generated manifest to
     this file. Intended only for debugging and testing. Checking in
     will fail if this file cannot be opened for writing.
  */
  char const * dumpManifestFile;
  /*
    fossil(1) has many more options. We might want to wrap some of
    it up in the "incremental" state (f->ckin.mf).

    TODOs:

    A callback mechanism which supports the user cancelling
    the checkin. It is (potentially) needed for ops like
    confirming the commit of CRNL-only changes.
  */
};

/**
   Empty-initialized fsl_checkin_opt instance, intended for use in
   const-copy constructing.
*/
#define fsl_checkin_opt_empty_m {               \
    NULL/*message*/,                            \
      NULL/*messageMimeType*/,                  \
      NULL/*user*/,                             \
      NULL/*branch*/,                           \
      NULL/*bgColor*/,                          \
      false/*isPrivate*/,                           \
      true/*calcRCard*/,                           \
      -1/*deltaPolicy*/,                        \
      false/*integrate*/,                           \
      0.0/*julianTime*/,                        \
      NULL/*closeBranch*/,                      \
      NULL/*dumpManifestFile*/                  \
      }

/**
   Empty-initialized fsl_checkin_opt instance, intended for use in
   copy-constructing. It is important that clients copy this value
   (or fsl_checkin_opt_empty_m) to cleanly initialize their
   fsl_checkin_opt instances, as this may set default values which
   (e.g.) a memset() would not.
*/
FSL_EXPORT const fsl_checkin_opt fsl_checkin_opt_empty;

/**
   Do not use - under construction/testing. Very dangerous. Stay
   away. If you choose to ignore this warning, then read on...

   This creates a "checkin manifest" for the current checkout.

   Its primary inputs is a list of files to commit. This list
   is provided by the client by calling fsl_checkin_file_enqueue()
   one or more times.  If no files are explicitely selected
   (enqueued) then it calculates which local files have changed vs
   the current checkout and selects all of those.

   Non-file inputs are provided via the opt parameter.

   Tip: to implement a "dry-run" mode, simply wrap this call in a
   transaction started on the fsl_cx_db_checkout() db handle
   (passing it to fsl_db_transaction_begin()), then, after this
   call, either cal; fsl_db_transaction_rollback() (to implement
   dry-run mode) or fsl_db_transaction_commit() (for "wet-run"
   mode). If this function returns non-0, such a transaction should
   _always_ be rolled back!

   On success, it returns 0 and...

   - If newRid is not NULL, it is assigned the new checkin's RID
   value.

   - If newUuid is not NULL, it is assigned the new checkin's UUID
   value. Ownership of the bytes is passed to the caller, who must
   eventually pass them to fsl_free() to free them.

   On error non-0 is returned and f's error state may (depending on
   the nature of the problem) contain details about the problem.
   Note, however, that any error codes returned here may have
   arrived from several layers down in the internals, and may not
   have a single specific interpretation here. When
   possible/practical, f's error state gets updated with a
   human-readable description of the problem.

   ACHTUNG: all pending checking state is cleaned if this function
   fails for any reason other than basic argument validation. This
   means any queued files or tags need to be re-applied if the
   client wants to try again. That is somewhat of a bummer, but this
   behaviour is the only way we can ensure that then the pending
   checkin state does not get garbled on a second use. When in doubt
   about the state, the client should call fsl_checkin_discard() to
   clear it before try to re-commit. (Potential TODO: add a
   success/fail state flag to the checkin state and only clean up on
   success? OTOH, since something in the state likely caused the
   problem, we might not want to do that.)

   This operation does all of its db-related work i a transaction,
   so it rolls back any db changes if it fails.

   Some of the more notable, potentially not obvious, error
   conditions:

   - Trying to commit against a closed leaf: FSL_RC_MISUSE

   - An empty/NULL user name or commit message, or no files were
   selected which actually changed: FSL_RC_RANGE. In these cases
   f's error state describes the problem.

   - Some resource is not found (e.g. an expected RID/UUID could not
   be resolved): FSL_RC_NOT_FOUND. This would generally indicate
   some sort of data consistency problem. i.e. it's quite possible
   very bad if this is returned.

   BUGS:

   - It cannot currently properly distinguish a "no-op" commit, one in
   which no files were modified. If, e.g.

   @see fsl_checkin_file_enqueue()
   @see fsl_checkin_file_dequeue()
   @see fsl_checkin_discard()
   @see fsl_checkin_T_add()
*/
FSL_EXPORT int fsl_checkin_commit(fsl_cx * f, fsl_checkin_opt const * opt,
                       fsl_id_t * newRid, fsl_uuid_str * newUuid);

/**
   Works like fsl_deck_T_add(), adding the given tag information to
   the pending checkin state. Returns 0 on success, non-0 on
   error. A checkin may, in principal, have any number of tags, and
   this may be called any number of times to add new tags to the
   pending commit. This list of tags gets cleared by a successful
   fsl_checkin_commit() or by fsl_checkin_discard().

   @see fsl_checkin_file_enqueue()
   @see fsl_checkin_file_dequeue()
   @see fsl_checkin_commit()
   @see fsl_checkin_discard()
*/
FSL_EXPORT int fsl_checkin_T_add( fsl_cx * f, fsl_tagtype_e tagType,
                       fsl_uuid_cstr uuid, char const * name,
                       char const * value);



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
















































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted include/fossil-scm/fossil-cli.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
/* -*- 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_FCLI_H_INCLUDED_)
#define _ORG_FOSSIL_SCM_FCLI_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 provides a basis for basic libfossil-using apps. It is intended
  to be used as the basis for test/demo apps, and not necessarily full-featured
  applications.
*/

/* Force assert() to always work... */
#if defined(NDEBUG)
#undef NDEBUG
#define DEBUG 1
#endif
#include "fossil-scm/fossil.h"
#include <assert.h> /* for the benefit of test apps */
#include <stdlib.h> /* EXIT_SUCCESS and friends */

/** @page page_fcli fcli (formerly FossilApp)

    ::fcli (formerly FossilApp) provides a small framework for
    bootstrapping simple libfossil applications which only need a
    single fsl_cx instance managing a single checkout and/or
    repository. It is primarily intended for use with CLI apps
    implementing features similar to those in fossil(1), but can also
    be used with GUI apps. It provides the following basic services to
    applications:

    - The global ::fcli struct holds global state.

    - fcli_setup() bootstraps the environment. This must be the
    first call made into the API, as this replaces the libfossil
    memory allocator with a fail-fast variant to simplify app-level
    code a bit (by removing the need to check for OOM errors). This
    also registers an atexit(3) handler to clean up fcli-owned
    resources at app shutdown.

    - Automatically tries to open a checkout (and its associated
    repository) under the current directory, but not finding one
    is not an error (the app needs to check for this if it should
    be an error: use fsl_cx_db_repo() and fsl_cx_db_ckout()).

    - fcli_flag(), fcli_next_arg(), and friends provide uniform access
    to CLI arguments.

    - A (very) basic help subsystem, triggered by the --help or -? CLI
    flags, or if the first non-flag argument is "help".  Applications
    may optionally assign fcli.appHelp to a function which outputs
    app-specific help.

    - Basic error reporting mechanism. See fcli_err_report().

    The source tree contains several examples of using fcli
    in the files named f-*.c.
*/

/**
   Ouputs the given printf-type expression (2nd arg) to
   fcli_printf() if fcli.verbose>=N, else it does nothing.

   Reminder: this uses a while(0) loop so that the macro can end
   with a semicolon without triggering a warning.
*/
#define FCLI_VN(N,pfexp)                        \
  if(fcli.clientFlags.verbose>=(N)) do {        \
      fcli_printf("VERBOSE %d: ", (int)(N));    \
      fcli_printf pfexp;                        \
  } while(0)

/**
   Convenience form of FCLI_VN for level-2 verbosity.
*/
#define FCLI_V2(pfexp) FCLI_VN(2,pfexp)

/**
   Convenience form of FCLI_VN for level-1 verbosity. Levels 1 and 2
   are intended for application-level use. Levels 3+ are intended for
   fcli use.
*/
#define FCLI_V(pfexp) FCLI_VN(1,pfexp)

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


/**
   Result codes specific to the fcli API.
*/
enum fcli_rc_e {
/**
   For use with fcli_flag_callback_f() implementations to indicate
   that the flag processor should check for that flag again.
*/
FCLI_RC_FLAG_AGAIN = FSL_RC_end + 1,
/**
   Returned from fcli_setup() if flag processing invokes the help
   system. This is an indication that the app should exit immediately
   with a 0 result code.
*/
FCLI_RC_HELP
};

/**
   Types for use with the fcli_cliflag::flagType field.  The value of
   that field determines how the CLI flag handling interprets the
   fcli_cliflag::flagValue void pointer.
*/
enum fcli_cliflag_type_e {
/** Sentinel placeholder. */
FCLI_FLAG_TYPE_INVALID = 0,
/** Represents bool. */
FCLI_FLAG_TYPE_BOOL,
/** Represents bool, but gets set to false if the flag is set. */
FCLI_FLAG_TYPE_BOOL_INVERT,
/** Represents int32_t. */
FCLI_FLAG_TYPE_INT32,
/** Represents int64_t. */
FCLI_FLAG_TYPE_INT64,
/** Represents fsl_id_t, which might be either int32_t or int64_t. */
FCLI_FLAG_TYPE_ID,
/** Represents double. */
FCLI_FLAG_TYPE_DOUBLE,
/** Represents (char const *). */
FCLI_FLAG_TYPE_CSTR
};

typedef struct fcli_cliflag fcli_cliflag;
/**
   Callback handler for CLI flag handling. It is passed the flag in
   question, containing its state after fcli has processed its
   flagValue part.

   If the callback returns FCLI_RC_FLAG_AGAIN, the flag handler will
   check for that flag again (for as long as the handler keeps
   returning that value and as long as the flag repeats in the
   argument list). This can be used to implement repeatable flags.

   If it returns any other non-0 value, fcli_setup()
   resp. fcli_process_flags() will fail with that result code.

   Achtung: during fcli_setup(), at the time these are processed, fcli
   will not yet have opened up a repository or checkout, so the
   callback will not have access to things like symbolic-name-to-UUID
   conversion at this level. Apps which need to do such work on their
   arguments must first queue up the input and process it after
   fcli_setup() returns. Apps which process the flags using
   fcli_process_flags() after setup is complete will have access to
   such features.
*/
typedef int (*fcli_flag_callback_f)(fcli_cliflag const *);

/**
   Under construction. A reworking of fcli's CLI flag handling,
   primarily so that we can unify the generation of app help text and
   make it consistent across apps.

   @see fcli_process_flags()
*/
struct fcli_cliflag {
  /**
     "Short-form" for this flag, noting that there's no restriction on
     its length. Either of flagShort and flagLong, but not both, may be
     NULL.
  */
  const char * flagShort;
  /**
     "Long-form" for this flag, noting that there's no restriction on
     its length.
  */
  const char * flagLong;

  /**
     Specifies how to interpret the data pointed to by
     this->flagValue. See that member for details.
  */
  enum fcli_cliflag_type_e flagType;

  /**
     If not NULL, the member is the target of the flag's value.
     Its exact interpretation depends on the value of this->flagType:

     - FCLI_FLAG_TYPE_BOOL: a pointer to a bool. Existence
     of the flag causes it to be set to true.

     - FCLI_FLAG_TYPE_BOOL_INVERT: a pointer to a bool, but
     existence of the flag causes it to be set to false.

     - FCLI_FLAG_TYPE_INT32: a pointer to an int32_t. The flag
     will be converted from string to int using atoi().

     - FCLI_FLAG_TYPE_INT64: a pointer to an int64_t. The flag
     will be converted from string to int using atoll().

     - FCLI_FLAG_TYPE_ID: a pointer to a fsl_id_t, which
     can be either int32_t or int64_t.

     - FCLI_FLAG_TYPE_DOUBLE: a pointer to a double. The flag
     will be converted from string to int using strtod().

     - FCLI_FLAG_TYPE_CSTR: a pointer to a (const char *). The flag's
     value will be assigned on without further interpretation.

     If this member is not NULL: If the flag is set, this value will
     be assigned to. If the flag is not set in the CLI args, this
     value is not written to.

     If this member is NULL then fcli_process_flags() will simply skip
     over it, but the help-text generator will process it. This can be
     used to set up flags which will appear in the --help text but
     which are processed separately (or outright ignored) by the app.

     The underlying flag value's string memory is owned by fcli and is
     valid until the app exits.
  */
  void * flagValue;

  /**
     Optional descriptive label to be used when rendering help text:

     -x|--x-flag=ABCD

     If it is NULL, the --help text generator will choose a value of
     the ABCD part which depends on this->flagType.

     This is only used if this->flagType is not one of
     (FCLI_FLAG_TYPE_BOOL, FCLI_FLAG_TYPE_BOOL_INVERT). For those
     types (which have no client-given values), this member is
     ignored when generating help text.
  */
  const char * flagValueLabel;

  /**
     Optional callback which gets passed the flag, if it is set, after
     fcli has assigned the flagValue entry (if it is not NULL).
     See this data type's docs for more details.
  */
  fcli_flag_callback_f callback;

  /**
     Help text for the flag. Intended to be displayed in the context
     of a --help listing of flags.
  */
  const char * helpText;
};
#define fcli_cliflag_empty_m {\
    0/*flagShort*/,0/*flagLong*/,\
    FCLI_FLAG_TYPE_INVALID/*flagType*/,\
    NULL/*flagValue*/,\
      NULL/*flagValueLabel*/,NULL/*callback*/,NULL/*helpText*/}
/** Non-const-copyable counterpart of fcli_cliflag_empty_m. */
FSL_EXPORT const fcli_cliflag fcli_cliflag_empty;

/** @def FCLI_FLAG_xxx

   The various FCLI_FLAG_xxx macros are convenience-form initializers
   for fcli_cliflag instances for use in initializing a fcli_cliflag
   array. Example usage:

   @code
   bool flag1 = true, flag2 = false;
   int32_t flag3 = 0;
   const char * flag4 = 0;
   const fcli_cliflag cliFlags[] = {
     FCLI_FLAG_BOOL("x","xyz",&flag1,"Flag 1."),
     FCLI_FLAG_BOOL_INVERT(NULL,"yzx",&flag2,"Flag 2."),
     FCLI_FLAG_INT32("z",NULL,"value",&flag3,"Flag 3"),
     FCLI_FLAG("f","file","filename",&flag4,
               "Input file. May optionally be passed as the first "
               "non-flag argument."),
     fcli_cliflag_empty_m // list MUST end with this (or equivalent)
   };
   fcli.cliFlags = cliFlags;
   @endcode

   BE CAREFUL with the data types, as we're using (void*) to access
   data of an arbitrary type, the type being defined by a separate
   field in the fcli_cliflag object.
*/
#define FCLI_FLAG_xxx /* for doc purposes only */
//Members:
//{short, long, type, tgt, valueDescr., callback, help}
/** Bool-type flag. */
#define FCLI_FLAG_BOOL(S,L,TGT,HELP) \
  {S,L,FCLI_FLAG_TYPE_BOOL,TGT,NULL,NULL,HELP}
/** Bool-type flag, but set to false if flag is set. */
#define FCLI_FLAG_BOOL_INVERT(S,L,TGT,HELP) \
  {S,L,FCLI_FLAG_TYPE_BOOL_INVERT,TGT,NULL,NULL,HELP}
/** Bool-type flag with a callback. */
#define FCLI_FLAG_BOOL_X(S,L,TGT,CALLBACK,HELP) \
  {S,L,FCLI_FLAG_TYPE_BOOL,TGT,NULL,CALLBACK,HELP}
/** Bool-type flag with a callback, but set to false if the flag is set. */
#define FCLI_FLAG_BOOL_INVERT_X(S,L,TGT,CALLBACK,HELP) \
  {S,L,FCLI_FLAG_TYPE_BOOL_INVERT,TGT,NULL,CALLBACK,HELP}
/** String-type flag. */
#define FCLI_FLAG_CSTR(S,L,LBL,TGT,HELP) \
  {S,L,FCLI_FLAG_TYPE_CSTR,TGT,LBL,NULL,HELP}
#define FCLI_FLAG FCLI_FLAG_CSTR
/** String-type flag with a callback. */
#define FCLI_FLAG_CSTR_X(S,L,LBL,TGT,CALLBACK,HELP) \
  {S,L,FCLI_FLAG_TYPE_CSTR,TGT,LBL,CALLBACK,HELP}
#define FCLI_FLAG_X FCLI_FLAG_CSTR_X
/** int32-type flag. */
#define FCLI_FLAG_I32(S,L,LBL,TGT,HELP) \
  {S,L,FCLI_FLAG_TYPE_INT32,TGT,LBL,NULL,HELP}
/** int32-type flag with a callback. */
#define FCLI_FLAG_I32_X(S,L,LBL,TGT,CALLBACK,HELP) \
  {S,L,FCLI_FLAG_TYPE_INT32,TGT,LBL,CALLBACK,HELP}
/** int32-type flag. */
#define FCLI_FLAG_I64(S,L,LBL,TGT,HELP) \
  {S,L,FCLI_FLAG_TYPE_INT64,TGT,LBL,NULL,HELP}
/** int32-type flag with a callback. */
#define FCLI_FLAG_I64_X(S,L,LBL,TGT,CALLBACK,HELP) \
  {S,L,FCLI_FLAG_TYPE_INT64,TGT,LBL,CALLBACK,HELP}
/** fsl_id_t-type flag. */
#define FCLI_FLAG_ID(S,L,LBL,TGT,HELP) \
  {S,L,FCLI_FLAG_TYPE_ID,TGT,LBL,NULL,HELP}
/** fsl_id_t-type flag with a callback. */
#define FCLI_FLAG_ID_X(S,L,LBL,TGT,CALLBACK,HELP) \
  {S,L,FCLI_FLAG_TYPE_ID,TGT,LBL,CALLBACK,HELP}
/** double-type flag. */
#define FCLI_FLAG_DBL(S,L,LBL,TGT,HELP) \
  {S,L,FCLI_FLAG_TYPE_DOUBLE,TGT,LBL,NULL,HELP}
/** double-type flag with a callback. */
#define FCLI_FLAG_DBL_X(S,L,LBL,TGT,CALLBACK,HELP) \
  {S,L,FCLI_FLAG_TYPE_DOUBLE,TGT,LBL,CALLBACK,HELP}

/**
   Structure for holding app-level --help info.
*/
struct fcli_help_info {
  /** Brief description of what the app does. */
  char const * briefDescription;
  /** Brief usage text. It will be prefixed by the app's name and
      "[options]". e.g. "file1 ... fileN".
  */
  char const * briefUsage;
  /**
     If not 0 then this is called after outputing any flags' help.
     It should output any additional help text using f_out().
  */
  void (*callback)(void);
};
typedef struct fcli_help_info fcli_help_info;

/**
   The fcli_t type, accessed by clients via the ::fcli
   global instance, contains various data for managing a basic
   Fossil SCM application build using libfossil.

   Usage notes:

   - All non-const pointer members are owned and managed by the
   fcli API. Clients may reference them but must be aware of
   lifetimes if they plan to hold the reference for long.

*/
struct fcli_t {
  /**
     If not NULL, it must be a pointer to fcli_help_info holding help
     info for the app.  It will be formated and output when --help
     is triggered.

     Should be set by client applications BEFORE calling fcli_setup()
     so that the ::fcli help subsystem can integrate the
     app. fcli.appName will be set by the time this is used.
  */
  fcli_help_info const * appHelp;

  /**
     May be set to an array of CLI flag objects, which fcli_setup()
     will use for parsing the CLI flags. The array MUST end with an
     entry which has NULL values for its (flagShort, flagLong)
     members. When creating the array it is simplest to use
     fcli_cliflag_empty_m as the initializer for that sentinel entry.

     The elements in this array are traversed in the order they are
     provided, and any which have a callback which returns
     FCLI_RC_FLAG_AGAIN will be traversed repeatedly before moving on
     to the next flag. This ordering may have side-effects on how the
     app sets up its flag handling. In particular, this arrangement
     makes the common cases easy to implement but makes certain more
     complicated situations effectively impossible to implement. For
     example:

     @code
     -x=1 -y=2 -x=3 -y=4
     @endcode

     Assuming the definition of the -y flag is first in this array and
     has a callback which returns FCLI_RC_FLAG_AGAIN, the two -x flags
     will be processed before either of the -y flags. Thus it is
     impossible (using this method of traversal) to change the
     behavior of the -y flags based on the left-closest -x value.

     We "could," but probably never will, instead walk the input argv
     looking for things which look like flags, then looking back into
     this list for a match, rather than the other way around. That
     would require a new args processing implementation, though, for
     relatively little benefit.
  */
  const fcli_cliflag * cliFlags;

  /**
     The shared fsl_cx instance. It gets initialized by
     fcli_setup() and cleaned up post-main().

     this->f is owned by this object and will be cleaned up at app
     shutdown (post-main).
  */
  fsl_cx * f;
  /**
     The current list of CLI arguments. This list gets modified by
     fcli_flag() and friends. Its memory is owned by fcli.
  */
  char ** argv;
  /**
     Current number of items in this->argv.
  */
  int argc;
  /**
     Application's name. Currently argv[0] but needs to be
     adjusted for Windows platforms.
  */
  char const * appName;
  /**
     The flags in this struct are "public," meaning that client
     applications may query and safely manipulate them directly if
     they know what they're doing.
  */
  struct {
    /**
       If not NULL then fcli_setup() will attempt to open the
       checkout for the given dir, including its associated repo
       db. By default this is "." (the current directory).

       Applications can set this to NULL _before_ calling
       fcli_setup() in order to disable the automatic attemp to
       open a checkout under the current directory.  Doing so is
       equivalent to using the --no-checkout|-C flags. The global
       --checkout-dir flag will trump that setting, though.
    */
    char const * checkoutDir;

    /**
       A verbosity level counter. Starts at 0 (no verbosity) and goes
       up for higher verbosity levels. Currently levels 1 and 2 are
       intended for app-level use and level 3 for library-level use.
    */
    unsigned short verbose;

    /**
       True if the -D|--dry-run flag is seen during initial arguments
       processing. ::fcli does not use this itself - it is
       intended as a convenience for applications.
    */
    bool dryRun;
  } clientFlags;
  /**
     Transient settings and flags. These are bits which are used
     during (or very shortly after) fcli_setup() but have no effect
     if modified after that.
  */
  struct {
    /**
       repo db name string from -R/--repo CLI flag.
    */
    const char * repoDbArg;
    /**
       User name from the -U/--user CLI flag.
    */
    const char * userArg;
    /**
       Incremented if fcli_setup() detects -? or --help in the
       argument list, or if the first non-flag argument is "help".
    */
    short helpRequested;
  } transient;
  /**
     Holds bits which can/should be configured by clients BEFORE
     calling fcli_setup().
  */
  struct {
    /**
       Whether or not to enable fossil's SQL tracing.  This should
       start with a negative value, which helps fcli_setup() process
       it. Setting this after initialization has no effect.
    */
    int traceSql;
    /**
       This output channel is used when initializing this->f. The
       default implementation uses fsl_outputer_FILE to output to
       stdout.
    */
    fsl_outputer outputer;

  } config;
  /**
     For holding pre-this->f-init error state. Once this->f is
     initialized, all errors reported via fcli_err_set() are stored in
     that object's error state.
  */
  fsl_error err;
};
typedef struct fcli_t fcli_t;

/** @var fcli

    This fcli_t instance is intended to act as a singleton.  It holds
    all fcli-related global state.  It gets initialized with
    default/empty state at app startup and gets fully initialized via
    fcli_setup(). See that routine for an example of how it is
    typically initialized.

    fcli_cx() returns the API's fsl_cx instance. It will be non-NULL
    (but might not have an opened checkout/repository) if fsl_setup()
    succeeds.
*/
FSL_EXPORT fcli_t fcli;

/**
   Should be called early on in main(), passed the arguments passed
   to main(). Returns 0 on success.  Sets up the ::fcli instance
   and opens a checkout in the current dir by default.

   MUST BE CALLED BEFORE fsl_malloc() and friends are used, as this
   swaps out the allocator with one which aborts on OOM. (But see
   fcli_pre_setup() for a workaround for that.)

   If argument processing finds either of the (--help, -?) flags,
   or the first non-flag argument is "help", it sets
   fcli.transient.helpRequested to a true value, calls fcli_help(),
   and returns FCLI_RC_HELP, in which case the application should
   exit/return from main with code 0 immediately.

   This function behaves significantly differently if fcli.cliFlags
   has been set before it is called. In that case, it parses the CLI
   flags using that type's rules and sets up fcli_help() to use those
   flags for generating the help. It parses the global flags first,
   then the app-specific flags.

   Returns 0 on success. Results other than FCLI_RC_HELP should be
   treated as fatal to the app, and fcli.f's error state _might_
   contain info about the error. If this function returns non-0, the
   convention is that the app immediately returns the result of
   fcli_end_of_main(THE_RESULT_CODE) from main(). That function will
   treat FCLI_RC_HELP as a non-error and will report any error state
   pending in the fcli_cx() object.

   Example of intended basic usage:

   @code
   int main(int argc, char const * const * argv){
     ...
     // Optional fcli_cliflag setup:
     fcli_cliflag const cliFlags[] = {
       ...,
       fcli_cliflag_empty_m
     };
     fcli.cliFlags = cliFlags;
     // Optional fcli_help_info setup:
     fcli_help_info const help = {
       "Fnoobs the borts and rustles the feathers.",
       "file1 [... fileN]",
       NULL // optional callback to display extra help
     };
     fcli.appHelp = &help;
     // Initialize...
     int rc = fcli_setup(argc, argv);
     if(rc) goto end;

     ... app logic ...

     end:
     return fcli_end_of_main(rc);
   }
   @endcode

   @see fcli_pre_setup()
*/
FSL_EXPORT int fcli_setup(int argc, char const * const * argv );

/**
   The first time this is called, it swaps out libfossil's default
   allocator with a fail-fast one which abort()s on allocation
   error. This is normally called by fcli_setup(), but that also means
   that it's illegal to use fsl_malloc() and friends before calling
   that routine. If an application really needs to use fsl_malloc()
   before calling fcli_setup(), it must call this first in order to
   get the allocator initialization out of the way.

   Calls after the first are no-ops, but the check for that is not
   thread-safe. Neither is fcli, though, so that's okay.
*/
FSL_EXPORT void fcli_pre_setup(void);

/**
   Returns the libfossil context associated with the fcli API.
   This will be NULL until fcli_setup() is called.
*/
FSL_EXPORT fsl_cx * fcli_cx(void);

/**
   Works like printf() but sends its output to fsl_outputf() using
   the fcli.f fossil conext (if set) or fsl_fprintf() (to
   stdout).
*/
FSL_EXPORT void fcli_printf(char const * fmt, ...)
#if 0
/* Would be nice, but complains about our custom format options: */
  __attribute__ ((__format__ (__printf__, 1, 2)))
#endif
  ;

/**
   f_out() is a shorthand for fcli_printf().
*/
#define f_out fcli_printf  

/**
   Returns the verbosity level set via CLI args. 0 is no verbosity,
   and one level is added each time the --verbose/-V CLI flag is
   encountered by fcli_setup().
*/
FSL_EXPORT unsigned short fcli_is_verbose(void);

/**
   Searches fcli.argv for the given flag (pass it without leading
   dashes). If found, this function returns true, else it returns
   false. If value is not NULL then the flag, if found, is assumed to
   have a value, otherwise the flag is assumed to be a boolean. A flag
   with a value may take either one of these forms:

   -flag=value
   -flag value

   *value gets assigned to a COPY OF the value part of the first form
   or a COPY OF the subsequent argument for the second form (copies
   are required in order to avoid trickier memory management
   here). That copy is owned by fcli and will be cleaned up at app
   exit. On success it removes the flag (and its value, if any) from
   fcli.argv.  Thus by removing all flags early on, the CLI arguments
   are left only with non-flag arguments to sift through.

   Flags may start with either one or two dashes - they are
   equivalent.

   This function may update the fcli error state if. Specifically, if
   passed a non-NULL 2nd argument and the flag is found at the end of
   the argument list or the value immediately after the flag starts
   with '-' then the error state will be updated and false will be
   returned. Apps don't normally need to be quite that picky with
   their error checking after looking for a flag, but the state is
   there if needed. It may get reset by any future calls into the API,
   though. fcli_process_flags() does check this state and will fail if
   such an error is triggered.
*/
FSL_EXPORT bool fcli_flag(char const * opt, const char ** value);

/**
   Works like fcli_flag() but tries two argument forms, in order. It
   is intended to be passed short and long forms, but can be passed
   two aliases or similar. It accepts NULL for either form.

   @code
   const char * v = NULL;
   fcli_flag2("n", "limit", &v);
   if(v) { ... }
   @endcode
*/
FSL_EXPORT bool fcli_flag2(char const * opt1, char const * opt2,
                           const char ** value);

/**
   Works similarly to fcli_flag2(), but if no flag is found and
   value is not NULL then *value is assigned to the return value of
   fcli_next_arg(true). In that case:

   - The return value will specify whether or not fcli_next_arg()
   returned a value or not.

   - If it returns true then *value is owned by fcli and will be
   cleaned up at app exit.

   - If it returns false, *value is not modified.

   The opt2 parameter may be NULL, but op1 may not.
*/
FSL_EXPORT bool fcli_flag_or_arg(char const * opt1, char const * opt2,
                                 const char ** value);


/**
   Clears any error state in fcli.f.
*/
FSL_EXPORT void fcli_err_reset(void);

/**
   Sets fcli.f's error state, analog to fsl_cx_err_set().
   Returns the code argument on success, some other non-0 value on
   a more serious error (e.g. FSL_RC_OOM when formatting the
   string).
*/
FSL_EXPORT int fcli_err_set(int code, char const * fmt, ...);

/**
   Returns the internally-used fsl_error instance which is used for
   propagating errors.  The object is owned by ::fcli and MUST NOT
   be freed or otherwise abused by clients. It may, however, be
   passed to routines which take a fsl_error parameter to report
   errors (e.g. fsl_deck_output().

   Returns NULL if fcli_setup() has not yet been called or after
   fcli has been cleaned up (post-main()).

*/
FSL_EXPORT fsl_error * fcli_error(void);

/**
   If ::fcli has any error state, this outputs it and returns the
   error code, else returns 0. If clear is true the error state is
   cleared/reset, otherwise it is left in place. Returns 0 if
   ::fcli has not been initialized. The 2nd and 3rd arguments are
   assumed to be the __FILE__ and __LINE__ macro values of the call
   point. See fcli_err_report() for a convenience form of this
   function.

   The format of the output depends partially on fcli_is_verbose(). In
   verbose mode, the file/line info is included, otherwise it is
   elided.

   @see fcli_err_report()
*/
FSL_EXPORT int fcli_err_report2(bool clear, char const * file, int line);

/**
   Convenience macro for using fcli_err_report2().
*/
#define fcli_err_report(CLEAR) fcli_err_report2((CLEAR), __FILE__, __LINE__)

/**
   Peeks at or takes the next argument from the CLI args.  If the
   argument is true, it is removed from the args list.  It is owned by
   fcli and will be freed when the app exits.
*/
FSL_EXPORT const char * fcli_next_arg(bool remove);

/**
   If fcli.argv contains what looks like any flag arguments, this
   updates the fossil error state and returns FSL_RC_MISUSE, else
   returns 0. If outputError is true and an unused flag is found
   then the error state is immediately output (but not cleared).
*/
FSL_EXPORT int fcli_has_unused_flags(bool outputError);
/**
   If fcli.argv contains any entries, returns FSL_RC_MISUSE and
   updates the error state with a message about unusued extra
   arguments, else returns 0. If outputError is true and an unconsumed
   argument is found then the error state is immediately output (but
   not cleared).
*/
FSL_EXPORT int fcli_has_unused_args(bool outputError);

typedef struct fcli_command fcli_command;
/**
   Typedef for general-purpose fcli call-by-name commands.

   It gets passed its own command definition, primarily for each of
   access to the flags member for CLI flags processing.

   @see fcli_dispatch_commands()
*/
typedef int (*fcli_command_f)(fcli_command const *);

/**
   Describes a named callback command.

   @see fcli_dispatch_commands()
*/
struct fcli_command {
  /** The name of the command. */
  char const * name;
  /** Brief description, for use in generating help text. */
  char const * briefDescription;
  /** The callback for this command. */
  fcli_command_f f;
  /**
     Must be NULL or an array compatible with fcli_process_flags().
     Can be used from within this->f for command-specific flag
     dispatching, as well as help text generation.
  */
  fcli_cliflag const * flags;
};

/**
   Expects an array of fcli_commands which contain a trailing
   sentry entry with a NULL name and callback. It searches the list
   for a command matching fcli_next_arg(). If found, it
   removes that argument from the list, calls the callback, and
   returns its result. If no command is found FSL_RC_NOT_FOUND is
   returned, the argument list is not modified, and the error state
   is updated with a description of the problem and a list of all
   command names in cmdList.

   If reportErrors is true then on error this function outputs
   the error result but it keeps the error state in place
   for the downstream use.

   As a special case: when a command matches the first argument and
   that object has a non-NULL flags member, this function checks the
   _next_ argument, and if it is "help" then this function passes
   that flags member to fcli_command_help() to output help, then
   returns 0.
*/
FSL_EXPORT int fcli_dispatch_commands( fcli_command const * cmdList,
                                       bool reportErrors);

/**
   A minor helper function intended to be passed the pending result
   code of the main() routine. This function outputs any pending
   error state in fcli. Returns one of EXIT_SUCCESS if mainRc is 0
   and fcli had no pending error report, otherwise it returns
   EXIT_FAILURE. This function does not clean up fcli - that is
   handled via an atexit() handler.

   It is intended to be called once at the very end of main:

   @code
   int main(){
   int rc;

   ...set up fcli...assign rc...

   return fcli_end_of_main(rc);
   }
   @endcode

   As a special case, if mainRc is FCLI_RC_HELP, it is assumed to
   be the result of the fcli --help flag handling, and is treated as
   if it were 0.

   @see fcli_error()
   @see fcli_err_set()
   @see fcli_err_report()
   @see fcli_err_reset()
*/
FSL_EXPORT int fcli_end_of_main(int mainRc);

/**
   If mem is not NULL, this routine appends mem to a list of pointers
   which will be passed to fsl_free() during the atexit() shutdown
   phase of the app. Because fcli uses a fail-fast allocator, failure
   to append the entry will itself cause a crash. This is only useful
   for values for which fsl_free() suffices to clean them up, not
   complex values like multi-dimensional arrays.

   "fax" is short for "free at exit."

   Results are undefined if the same address or overlapping addresses
   are queued more than once. Once an entry is in this queue, there is
   no way to remove it.
*/
FSL_EXPORT void fcli_fax(void * mem);

/**
   Requires an array of fcli_cliflag objects terminated with an
   instance with NULL values for the (flagShort, flagLong) members
   (fcli_cliflag_empty_m is an easy way to get that).

   If fcli.cliFlags is set before fcli_setup() is called, this routine
   is called and passed those flags. Thus most apps can simply assign
   their flags there and let setup() do the work. Apps which have
   multiple dispatch paths, each with differing flags, may find it
   easier to use this.

   As a special case, if a given entry has NULL values for both of its
   (flagValue, callback) members, it is assumed to exist purely for
   use with the help-generating mechanisms and the flag is NOT
   processed or consumed by fcli_process_flags(). That can be used
   when the client needs to process the flag in ways beyond what is
   capable via this routine. In such cases, add an appropriate entry
   to the fcli_cliflag array for --help purposes and then process the
   flag using fcli_flag() (or similar) either before or after calling
   this.

   Returns 0 on success. Returns non-0 only if a callback() member of
   one of the entries returns a value other than FCLI_RC_FLAG_AGAIN.
*/
FSL_EXPORT int fcli_process_flags( fcli_cliflag const * defs );

/**
   Requires an array of fcli_cliflag objects as described for
   fcli_process_flags(). This routine outputs their flags
   and help text in a framework-conventional manner.

   If fcli.cliFlags is set before fcli_setup() is called, this routine
   is called and passed those flags. Thus most apps can simply assign
   their flags there and let fcli_setup() do the work. Apps which have
   multiple dispatch paths, each with differing flags, may find it
   easier to use this.

   Such apps, in order to get the full help listing for all dispatch
   paths, may need to assign fcli.appHelp to a helper which
   dispatches to an app-local help routine which, in turn, passes each
   of their separate fcli_cliflag lists to this routine.
*/
FSL_EXPORT void fcli_cliflag_help(fcli_cliflag const *defs);

/**
   Requires that cmd be an array of fcli_command objects with a
   trailing entry which has a NULL name. This function iterates over
   them and outputs help text based on each one's (name,
   briefDescription, flags) members.

   If the 2nd argument is true, only the help for the single given
   object is output, not any adjacent array members (if any).
*/
FSL_EXPORT void fcli_command_help(fcli_command const * cmd, bool onlyOne);

/**
   If fcli has a checkout opened, this dumps various info about it
   to its output channel. Returns 0 on success, FSL_RC_NOT_A_CKOUT
   if no checkout is opened, or some other non-0 code on error.

   If the useUtc argument is true, it uses UTC timestamps, else
   localtime. (Potential TODO: add an fcli-level CLI flag for that
   instead?)
*/
FSL_EXPORT int fcli_ckout_show_info(bool useUtc);

/**
   Given a hash prefix, lists (via f_out()) all blob table entries
   which have this prefix. This is intended to be used by apps which
   accept a from a user and fsl_sym_to_rid() returns FSL_RC_AMBIGUOUS.

   The first argument is an optional output label/header to output.
   If it is NULL, a default is used. If it is "" then no header is
   output.
*/
FSL_EXPORT void fcli_list_ambiguous_artifacts(char const * label, char const *prefix);

/**
   If fcli has an opened checkout, that db handle is returned, else NULL
   is returned.
*/
FSL_EXPORT fsl_db * fcli_db_ckout(void);

/**
   If fcli has an opened repository, that db handle is returned, else
   NULL is returned.
*/
FSL_EXPORT fsl_db * fcli_db_repo(void);

/**
   If fcli has an opened checkout, that db handle is returned, else NULL
   is returned and fcli's error state is updated with a description
   of the problem.   
*/
FSL_EXPORT fsl_db * fcli_needs_ckout(void);

/**
   If fcli has an opened repository, that db handle is returned, else NULL
   is returned and fcli's error state is updated with a description
   of the problem.   
*/
FSL_EXPORT fsl_db * fcli_needs_repo(void);

/**
   Processes all remaining CLI arguments as potential file or directory
   names, collects their vfile.id values, and stores them in the given
   target bag. It requires an opened checkout.

   vid is the vfile.vid value to filter on. If vid<=0 then the current
   checkout version is used. (Unless the app has explicitly loaded
   another version, that will be the only option available.)

   If relativeToCwd is true then each argument is resolved as if
   referenced from the current working directory, else each is assumed
   to be relative to the top of the checkout directory. (For CLI apps,
   a value of true is almost always the right choice.)

   If changedFilesOnly is true then only files which are "changed",
   according to the vfile table (as opposed to a filesystem check) are
   considered for addition. For that to work, vfile must be up to
   date, so fsl_vfile_changes_scan() must have been recently called to
   update that state. This function does not call it automatically
   because it's relatively slow and many apps already have to call it
   on their own.

   This function matches only vfile.pathname, not vfile.origname,
   because it is possible for a given name to be in both fields (in
   different records) at the same time.

   Returns 0 on success. If there are no more CLI arguments when it is
   called then it returns FSL_RC_MISUSE and updates the fcli error
   state with a description of the problem. It may return any number
   of non-0 codes from the underlying operations.

   Sidebar: fsl_filename_to_vfile_ids() requires that directory names
   passed to it have no trailing slashes, and routine strips trailing
   slashes from its arguments before passing them on to that routine,
   so they may be entered with slashes without ill effect.

   @see fsl_filename_to_vfile_ids()
*/
FSL_EXPORT int fcli_args_to_vfile_ids(fsl_id_bag *tgt, fsl_id_t vid,
                                      bool relativeToCwd,
                                      bool changedFilesOnly);

/**
   Performs a "fingerprint check" on the current checkout/repo
   combination, as per fsl_ckout_fingerprint_check(). If the check
   fails and reportImmediately is true then an error report is
   immediately output. Returns 0 if the fingerprint check is okay,
   else a non-0 value as per fsl_ckout_fingerprint_check().

   Passing true here may output more information than the underlying
   fsl_cx-level error state provides. e.g. it may provide a hint about
   how to recover.
*/
FSL_EXPORT int fcli_fingerprint_check(bool reportImmediately);

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


#endif
/* _ORG_FOSSIL_SCM_FCLI_H_INCLUDED_ */
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted include/fossil-scm/fossil-confdb.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
/* -*- 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_CONFDB_H_INCLUDED)
#define ORG_FOSSIL_SCM_FSL_CONFDB_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).
*/
/** @file fossil-confdb.h

    fossil-confdb.h declares APIs dealing with fossil's various
    configuration option storage backends.
*/

#include "fossil-core.h" /* MUST come first b/c of config macros */

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

/**
   A flag type for specifying which configuration backend a given API
   should be applied to. Used by most of the fsl_config_XXX() APIS,
   e.g. fsl_config_get_int32() and friends.

   This type is named fsl_confdb_e (not the "db" part) because 3 of
   the 4 fossil-supported config storage backends are databases. The
   "versioned setting" backend, which deals with non-db local files,
   was encapsulated into this API long after the db-related options
   were
*/
enum fsl_confdb_e {
/**
   Signfies the global-level (per system user) configuration area.
*/
FSL_CONFDB_GLOBAL = 1,
/**
   Signfies the repository-level configuration area.
*/
FSL_CONFDB_REPO = 2,
/**
   Signfies the checkout-level (a.k.a. "local") configuration area.
*/
FSL_CONFDB_CKOUT = 3,

/**
   The obligatory special case...

   Versionable settings are stored directly in SCM-controlled files,
   each of which has the same name as the setting and lives in the
   .fossil-settings directory of a checkout. Though versionable
   settings _can_ be read from a non-checked-out repository, doing so
   requires knowning which version to fetch and is horribly
   inefficient, so there are currently no APIs for doing so.

   Note that the APIs which read and write versioned settings do not
   care whether those settings are valid for fossil(1).

   Reminder to self: SQL to find the checkins in which a versioned
   settings file was added or modified (ignoring renames and branches
   and whatnot).

   @code
select strftime('%Y-%m-%d %H:%M:%S',e.mtime) mtime,
b.rid, b.uuid from mlink m, filename f, blob b, event e
where f.fnid=m.fnid
and m.mid=b.rid
and b.rid=e.objid
and f.name='.fossil-settings/ignore-glob'
order by e.mtime desc 
;
   @endcode

*/
FSL_CONFDB_VERSIONABLE = 4
};
typedef enum fsl_confdb_e fsl_confdb_e;

/**
   Returns the name of the db table associated with the given
   mode. Results are undefined if mode is an invalid value. The
   returned bytes are static and constant.

   Returns NULL for the role FSL_CONFDB_VERSIONABLE.
*/
FSL_EXPORT char const * fsl_config_table_for_role(fsl_confdb_e mode);

/**
   Returns a handle to the db associates with the given fsl_confdb_e
   value. Returns NULL if !f or if f has no db opened for that
   configuration role. Results are undefined if mode is an invalid
   value.

   For FSL_CONFDB_VERSIONABLE it returns the results of fsl_cx_db(),
   even though there is no database-side support for versionable files
   (which live in files in a checkout).
*/
FSL_EXPORT fsl_db * fsl_config_for_role(fsl_cx * f, fsl_confdb_e mode);

/**
   Returns the int32 value of a property from one of f's config
   dbs, as specified by the mode parameter. Returns dflt if !f, f
   does not have the requested config db opened, no entry is found,
   or on db-level errors.
*/
FSL_EXPORT int32_t fsl_config_get_int32( fsl_cx * f, fsl_confdb_e mode,
                                         int32_t dflt, char const * key );
/**
   int64_t counterpart of fsl_config_get_int32().
*/
FSL_EXPORT int64_t fsl_config_get_int64( fsl_cx * f, fsl_confdb_e mode,
                                         int64_t dflt, char const * key );

/**
   fsl_id_t counterpart of fsl_config_get_int32().
*/
FSL_EXPORT fsl_id_t fsl_config_get_id( fsl_cx * f, fsl_confdb_e mode,
                                       fsl_id_t dflt, char const * key );
/**
   double counterpart of fsl_config_get_int32().
*/
FSL_EXPORT double fsl_config_get_double( fsl_cx * f, fsl_confdb_e mode,
                                         double dflt, char const * key );


/**
   Boolean countertpart of fsl_config_get_int32().

   fsl_str_bool() is used to determine the booleanness (booleanity?)
   of a given config option.
*/
FSL_EXPORT bool fsl_config_get_bool( fsl_cx * f, fsl_confdb_e mode,
                                     bool dflt, char const * key );

/**
   A convenience form of fsl_config_get_buffer().  If it finds a
   config entry it returns its value. If *len is not NULL then *len is
   assigned the length of the returned string, in bytes (and is set to
   0 if NULL is returned). Any errors encounters while looking for
   the entry are suppressed and NULL is returned.

   The returned memory must eventually be freed using fsl_free(). If
   len is not NULL then it is set to the length of the returned
   string. Returns NULL for any sort of error or for a NULL db value.

   If capturing error state is important for a given use case, use
   fsl_config_get_buffer() instead, which provides the same features
   as this one but propagates any error state.
*/
FSL_EXPORT char * fsl_config_get_text( fsl_cx * f, fsl_confdb_e mode,
                                       char const * key,
                                       fsl_size_t * len );

/**
   The fsl_buffer-type counterpart of fsl_config_get_int32().

   Replaces the contents of the given buffer (re-using any memory it
   might have) with a value from a config region. Returns 0 on
   success, FSL_RC_NOT_FOUND if no entry was found or the requested db
   is not opened, FSL_RC_OOM on allocation errror.

   If mode is FSL_CONFDB_VERSIONABLE, this operation requires
   a checkout and returns (if possible) the contents of the file
   named {CHECKOUT_ROOT}/.fossil-settings/{key}.

   On any sort of error, including the inability to find or open
   a versionable-settings file, non-0 is returned:

   - FSL_RC_OOM on allocation error

   - FSL_RC_NOT_FOUND if either the database referred to by mode is
   not opened or a matching setting cannot be found.

   - FSL_RC_ACCESS is a versionable setting file is found but cannot
   be opened for reading.

   - FSL_RC_NOT_A_CKOUT if mode is FSL_CONFDB_VERSIONABLE and no
   checkout is opened.

   - Potentially one of several db-related codes if reading a
   non-versioned setting fails.

   In the grand scheme of things, the inability to load a setting is
   not generally an error, so clients are not expected to treat it as
   fatal unless perhaps it returns FSL_RC_OOM, in which case it likely
   is.

   @see fsl_config_has_versionable()
*/
FSL_EXPORT int fsl_config_get_buffer( fsl_cx * f, fsl_confdb_e mode,
                                      char const * key, fsl_buffer * b );


/**
   If f has an opened checkout, this replaces b's contents (re-using
   any existing memory) with an absolute path to the filename for that
   setting: {CHECKOUT_ROOT}/.fossil-settings/{key}

   This routine neither verifies that the given key is a valid
   versionable setting name nor that the file exists: it's purely a
   string operation.

   Returns 0 on success, FSL_RC_NOT_A_CKOUT if f has no checkout
   opened, FSL_RC_MISUSE if key is NULL, empty, or if
   fsl_is_simple_pathname() returns false for the key, and FSL_RC_OOM
   if appending to the buffer fails.
 */
FSL_EXPORT int fsl_config_versionable_filename(fsl_cx *f, char const * key, fsl_buffer *b);

/**
   Sets a configuration variable in one of f's config databases, as
   specified by the mode parameter. Returns 0 on success.  val may
   be NULL. Returns FSL_RC_MISUSE if !f, f does not have that
   database opened, or !key, FSL_RC_RANGE if !key.

   If mode is FSL_CONFDB_VERSIONABLE, it attempts to write/overwrite
   the corresponding file in the current checkout.

   If mem is NULL and mode is not FSL_CONFDB_VERSIONABLE then an SQL
   NULL is bound instead of an empty blob. For FSL_CONFDB_VERSIONABLE
   an empty file will be written for that case.

   If mode is FSL_CONFDB_VERSIONABLE, this does NOT queue any
   newly-created versionable setting file for inclusion into the
   SCM. That is up to the caller. See
   fsl_config_versionable_filename() for info about an additional
   potential usage error case with FSL_CONFDB_VERSIONABLE.

   Pedantic side-note: the input text is saved as-is. No trailing
   newline is added when saving to FSL_CONFDB_VERSIONABLE because
   doing so would require making a copy of the input bytes just to add
   a newline to it. The non-text fsl_config_set_XXX() APIs add a
   newline when writing to their values out to a versionable config
   file because it costs them nothing to do so and text files "should"
   have a trailing newline.

   Potential TODO: if mode is FSL_CONFDB_VERSIONABLE and the key
   contains directory components, e,g, "app/x", we should arguably use
   fsl_mkdir_for_file() to create those components. As of this writing
   (2021-03-14), no such config keys have ever been used in fossil.

   @see fsl_config_versionable_filename()
*/
FSL_EXPORT int fsl_config_set_text( fsl_cx * f, fsl_confdb_e mode, char const * key, char const * val );

/**
   The blob counterpart of fsl_config_set_text(). If len is
   negative then fsl_strlen(mem) is used to determine the length of
   the memory.

   If mem is NULL and mode is not FSL_CONFDB_VERSIONABLE then an SQL
   NULL is bound instead of an empty blob. For FSL_CONFDB_VERSIONABLE
   an empty file will be written for that case.
*/
FSL_EXPORT int fsl_config_set_blob( fsl_cx * f, fsl_confdb_e mode, char const * key,
                                    void const * mem, fsl_int_t len );
/**
   int32 counterpart of fsl_config_set_text().
*/
FSL_EXPORT int fsl_config_set_int32( fsl_cx * f, fsl_confdb_e mode,
                                     char const * key, int32_t val );
/**
   int64 counterpart of fsl_config_set_text().
*/
FSL_EXPORT int fsl_config_set_int64( fsl_cx * f, fsl_confdb_e mode,
                                     char const * key, int64_t val );
/**
   fsl_id_t counterpart of fsl_config_set_text().
*/
FSL_EXPORT int fsl_config_set_id( fsl_cx * f, fsl_confdb_e mode,
                                  char const * key, fsl_id_t val );
/**
   fsl_double counterpart of fsl_config_set_text().
*/
FSL_EXPORT int fsl_config_set_double( fsl_cx * f, fsl_confdb_e mode,
                                      char const * key, double val );
/**
   Boolean counterpart of fsl_config_set_text().

   For compatibility with fossil conventions, the value will be saved
   in the string form "on" or "off". When mode is
   FSL_CONFDB_VERSIONABLE, that value will include a trailing newline.
*/
FSL_EXPORT int fsl_config_set_bool( fsl_cx * f, fsl_confdb_e mode,
                                    char const * key, bool val );

/**
   "Unsets" (removes) the given key from the given configuration database.
   It is not considered to be an error if the config table does not
   contain that key.

   Returns FSL_RC_UNSUPPORTED, without side effects, if mode is
   FSL_CONFDB_VERSIONABLE. It "could" hypothetically remove a
   checked-out copy of a versioned setting, then queue the file for
   removal in the next checkin, but it does not do so. It might, in
   the future, be changed to do so, or at least to remove the local
   settings file.
*/
FSL_EXPORT int fsl_config_unset( fsl_cx * f, fsl_confdb_e mode,
                                 char const * key );

/**
   Begins (or recurses) a transaction on the given configuration
   database.  Returns 0 on success, non-0 on error. On success,
   fsl_config_transaction_end() must eventually be called with the
   same parameters to pop the transaction stack. Returns
   FSL_RC_MISUSE if no db handle is opened for the given
   configuration mode. Assuming all arguments are valid, this
   returns the result of fsl_db_transaction_end() and propagates
   any db-side error into the f object's error state.

   This is primarily intended as an optimization when an app is
   making many changes to a config database. It is not needed when
   the app is only making one or two changes.

   @see fsl_config_transaction_end()
   @see fsl_db_transaction_begin()
*/
FSL_EXPORT int fsl_config_transaction_begin(fsl_cx * f, fsl_confdb_e mode);

/**
   Pops the transaction stack pushed by
   fsl_config_transaction_begin(). If rollback is true then the
   transaction is set roll back, otherwise it is allowed to
   continue (if recursive) or committed immediately (if not
   recursive).  Returns 0 on success, non-0 on error. Returns
   FSL_RC_MISUSE if no db handle is opened for the given
   configuration mode. Assuming all arguments are valid, this
   returns the result of fsl_db_transaction_end() and propagates
   any db-side error into the f object's error state.

   @see fsl_config_transaction_begin()
   @see fsl_db_transaction_end()
*/
FSL_EXPORT int fsl_config_transaction_end(fsl_cx * f, fsl_confdb_e mode, char rollback);

/**
   Populates li as a glob list from the given configuration key.
   Uses (versionable/repo/global) config settings, in that order.
   It is not an error if one or more of those sources is missing -
   they are simply skipped.

   Note that gets any new globs appended to it, as per
   fsl_glob_list_append(), as opposed to replacing any existing
   contents.

   Returns 0 on success, but that only means that there were no
   errors, not that any entries were necessarily added to li.

   Arguably a bug: this function does not open the global config if
   it was not already opened, but will use it if it is opened. This
   function should arbuably open and close it in that case.
*/
FSL_EXPORT int fsl_config_globs_load(fsl_cx * f, fsl_list * li, char const * key);

/**
   Fetches the preferred name of the "global" db file for the current
   user by assigning it to *zOut. Returns 0 on success, in which case
   *zOut is updated and non-0 on error, in which case *zOut is not
   modified.  On success, ownership of *zOut is transferred to the
   caller, who must eventually free it using fsl_free().

   The locations searched for the database file are
   platform-dependent...

   Unix-like systems are searched in the following order:

   1) If the FOSSIL_HOME environment var is set, use
   $FOSSIL_HOME/.fossil.

   2) If $HOME/.fossil already exists, use that.

   3) If XDG_CONFIG_HOME environment var is set, use
   $XDG_CONFIG_HOME/fossil.db.

   4) If $HOME/.config is a directory, use $HOME/.config/fossil.db

   5) Fall back to $HOME/.fossil (historical name).

   Except where listed above, this function does not check whether the
   file already exists or is a database.

   Windows:

   - We need a Windows port of this routine. Currently it simply uses
   the Windows home directory + "/_fossil" or "/.fossil", depending on
   the build-time environment.
*/
FSL_EXPORT int fsl_config_global_preferred_name(char ** zOut);

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


































































































































































































































































































































































































































































































































































































































































































































































































































Deleted include/fossil-scm/fossil-config.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#if !defined (ORG_FOSSIL_SCM_FSL_CONFIG_H_INCLUDED)
#define ORG_FOSSIL_SCM_FSL_CONFIG_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).

*/
#if defined(_MSC_VER) && !defined(FSL_AMALGAMATION_BUILD)
#  include "config-win32.h" /* manually generated */
#else
#  include "autoconfig.h" /* auto-generated */
#endif

#ifdef _WIN32
# if defined(BUILD_libfossil_static) || defined(FSL_AMALGAMATION_BUILD)
#  define FSL_EXPORT extern
# elif defined(BUILD_libfossil)
#  define FSL_EXPORT extern __declspec(dllexport)
# else
#  define FSL_EXPORT extern __declspec(dllimport)
# endif
#else
# define FSL_EXPORT extern
#endif

#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L)
#  if defined(__cplusplus) && !defined(__STDC_FORMAT_MACROS)
/* inttypes.h needs this for the PRI* and SCN* macros in C++ mode. */
#    define __STDC_FORMAT_MACROS
#  else
#    error "This tree requires a standards-compliant C99-capable compiler."
#  endif
#endif

#include <stdint.h>
#include <inttypes.h>

#if !defined(FSL_AUX_SCHEMA)
#error "Expecting FSL_AUX_SCHEMA to be defined by the configuration bits."
#endif
#if !defined(FSL_LIBRARY_VERSION)
#error "Expecting FSL_LIBRARY_VERSION to be defined by the configuration bits."
#endif


/** @typedef some_int_type fsl_int_t

    fsl_int_t is a signed integer type used to denote "relative"
    ranges and lengths, or to tell a routine that it should try to
    figure out the length of some byte array itself (e.g.  by using
    fsl_strlen() on it). It is provided primarily for
    documentation/readability purposes, to avoid confusion with the
    widely varying integer semantics used by various APIs. This type
    is never used as a return type for functions which use "result code
    semantics." Those always use an unadorned integer type or some
    API-specific enum type.

    The library typedefs this to a 64-bit type if possible, else
    a 32-bit type.
*/
typedef int64_t fsl_int_t;
/**
    The unsigned counterpart of fsl_int_t.
 */
typedef uint64_t fsl_uint_t;
/** @def FSL_INT_T_PFMT

    Fossil's fsl_int_t equivalent of C99's PRIi32 and friends.
 */
#define FSL_INT_T_PFMT PRIi64
/** @def FSL_INT_T_SFMT

    Fossil's fsl_int_t equivalent of C99's SCNi32 and friends.
 */
#define FSL_INT_T_SFMT SCNi64
/** @def FSL_UINT_T_PFMT

    Fossil's fsl_uint_t equivalent of C99's PRIu32 and friends.
 */
#define FSL_UINT_T_PFMT PRIu64
/** @def FSL_UINT_T_SFMT

    Fossil's fsl_uint_t equivalent of C99's SCNu32 and friends.
 */
#define FSL_UINT_T_SFMT SCNu64

/** @def FSL_JULIAN_T_PFMT

    An output format specifier for Julian-format doubles.
 */
#define FSL_JULIAN_T_PFMT ".17g"

/** 
    fsl_size_t is an unsigned integer type used to denote absolute
    ranges and lengths. It is provided primarily for
    documentation/readability purposes, to avoid confusion with the
    widely varying integer semantics used by various APIs. While a
    32-bit type is legal, a 64-bit type is required for "unusually
    large" repos and for some metrics reporting even for mid-sized
    repos.
 */
typedef uint64_t fsl_size_t;

/** @def FSL_SIZE_T_PFMT

    Fossil's fsl_size_t equivalent of C99's PRIu32 and friends.

    ACHTUNG: when passing arguments of this type of fsl_appendf(), or
    any function which uses it for formatting purposes, it is very
    important if if you pass _literal integers_ OR enum values, that
    they be cast to fsl_size_t, or the va_list handling might extract
    the wrong number of bytes from the argument list, leading to
    really weird side-effects via what is effectively memory
    corruption.

    That warning applies primarily to the following typedefs and their
    format specifiers: fsl_size_t, fsl_int_t, fsl_uint_t, fsl_id_t.

    The warning does not apply to strongly-typed arguments,
    e.g. variables of the proper type, so long as the format specifier
    string matches the argument type.

    For example:

    @code
    fsl_size_t sz = 3;
    fsl_fprintf( stdout, "%"FSL_SIZE_T_PFMT" %"FSL_SIZE_T_PFMT\n",
                 sz, // OK!
                 3 // BAD! See below...
                 );
    @endcode

    The "fix" is to cast the literal 3 to a fsl_size_t resp. the type
    appropriate for the format specifier. That ensures that there is
    no (or much less ;) confusion when va_arg() extracts arguments
    from the variadic array.

    Reminders to self:

    @code
    int i = 0;
    f_out(("#%d: %"FSL_ID_T_PFMT" %"FSL_ID_T_PFMT" %"FSL_ID_T_PFMT"\n",
            ++i, 1, 2, 3));
    f_out(("#%d: %"FSL_SIZE_T_PFMT" %"FSL_ID_T_PFMT" %"FSL_SIZE_T_PFMT"\n",
           ++i, (fsl_size_t)1, (fsl_id_t)2, (fsl_size_t)3));
    // This one is the (generally) problematic case:
    f_out(("#%d: %"FSL_SIZE_T_PFMT" %"FSL_ID_T_PFMT" %"FSL_SIZE_T_PFMT"\n",
           ++i, 1, 2, 3));
    @endcode

    The above was Tested with gcc, clang, tcc on a 32-bit linux
    platform (it has not been problematic on 64-bit builds!). The
    above problem was reproduced on all compiler combinations i
    tried. Current code (20130824) seems to be behaving well as long
    as callers always cast to help variadic arg handling DTRT.
 */
#define FSL_SIZE_T_PFMT FSL_UINT_T_PFMT

/** @def FSL_SIZE_T_SFMT

    Fossil's fsl_int_t equivalent of C99's SCNu32 and friends.
 */
#define FSL_SIZE_T_SFMT FSL_UINT_T_SFMT

/**
    fsl_id_t is a signed integer type used to store database record
    IDs. It is provided primarily for documentation/readability purposes,
    to avoid confusion with the widely varying integer semantics used
    by various APIs.

    This type "could" be 32-bit (instead of 64) because the
    oldest/largest Fossil repo (the TCL tree, with 15 years of
    history) currently (August 2013) has only 131k RIDs. HOWEVER,
    changing this type can have side-effects vis-a-vis va_arg() deep
    in the fsl_appendf() implementation if FSL_ID_T_PFMT is not 100%
    correct for this typedef. After changing this, _make sure_ to do a
    full clean rebuild and test thoroughly because changing a sizeof
    can produce weird side-effects (effectively memory corruption) on
    unclean rebuilds.
 */
typedef int32_t fsl_id_t;

/** @def FSL_ID_T_PFMT

    Fossil's fsl_id_t equivalent of C99's PRIi32 and friends.

    ACHTUNG: see FSL_SIZE_T_PFMT for important details.
 */
#define FSL_ID_T_PFMT PRIi32

/** @def FSL_ID_T_SFMT

    Fossil's fsl_id_t equivalent of C99's SCNi32 and friends.
 */
#define FSL_ID_T_SFMT SCNi32

/**
    The type used to represent type values. Unless noted otherwise,
    the general convention is Unix Epoch. That said, Fossil internally
    uses Julian Date for times, so this typedef is clearly the result
    of over-specification/over-thinking the problem. THAT said,
    application-level code more commonly works with Unix timestamps,
    so... here it is. Over-specified, perhaps, but not 100%
    unjustifiable.
 */
typedef int64_t fsl_time_t;

/** @def FSL_TIME_T_PFMT

    Fossil's fsl_time_t equivalent of C99's PRIi32 and friends.
 */
#define FSL_TIME_T_PFMT PRIi64

/** @def FSL_TIME_T_SFMT

    Fossil's fsl_time_t equivalent of C99's SCNi32 and friends.
 */
#define FSL_TIME_T_SFMT SCNi64

/**
   If true, the fsl_timer_xxx() family of functions might do something useful,
   otherwise they do not.
 */
#define FSL_CONFIG_ENABLE_TIMER 1


#endif
/* ORG_FOSSIL_SCM_FSL_CONFIG_H_INCLUDED */
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




















































































































































































































































































































































































































































































Deleted include/fossil-scm/fossil-core.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
/* -*- 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_CORE_H_INCLUDED)
#define ORG_FOSSIL_SCM_FSL_CORE_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).

*/

/* @file fossil-core.h

  This file declares the core SCM-related public APIs.
*/

#include "fossil-scm/fossil-util.h" /* MUST come first b/c of config macros */
#include <time.h> /* struct tm, time_t */
#if defined(__cplusplus)
/**
  The fsl namespace is reserved for an eventual C++ wrapper for the API.
*/
namespace fsl {}
extern "C" {
#endif

/**
   @struct fsl_cx

   The main Fossil "context" type. This is the first argument to
   many Fossil library API routines, and holds all state related
   to a checkout and/or repository and/or global fossil configuration
   database(s).

   An instance's lifetime looks something like this:

   @code
   int rc;
   fsl_cx * f = NULL; // ALWAYS initialize to NULL or results are undefined
   rc = fsl_cx_init( &f, NULL );
   assert(!rc);
   rc = fsl_repo_open( f, "myrepo.fsl" );
   ...use the context, and clean up when done...
   fsl_cx_finalize(f);
   @endcode

   The contents of an fsl_cx instance are strictly private, for use
   only by APIs in this library. Any client-side dependencies on
   them will lead to undefined behaviour at some point.

   Design note: this type is currently opaque to client
   code. Having it non-opaque also has advantages, though, and i'd
   generally prefer that (to allow client-side allocation and
   embedding in other structs). Binary compatibility concerns might
   force us to keep it opaque.

*/

typedef struct fsl_cx fsl_cx;
typedef struct fsl_cx_config fsl_cx_config;
typedef struct fsl_db fsl_db;
typedef struct fsl_cx_init_opt fsl_cx_init_opt;
typedef struct fsl_stmt fsl_stmt;

/**
   This enum defines type ID tags with which the API tags fsl_db
   instances so that the library can figure out which DB is
   which. This is primarily important for certain queries, which
   need to know whether they are accessing the repo or config db,
   for example.

   All that said, i'm not yet fully convinced that a straight port of
   the fossil model is the best option for how we internally manage
   DBs, so this is subject to eventual change or removal.

   @see fsl_db_role_label()
   @see fsl_cx_db_name_for_role()
*/
enum fsl_dbrole_e {
/**
   Sentinel "no role" value.
*/
FSL_DBROLE_NONE = 0,
/**
   Analog to fossil's "configdb".
*/
FSL_DBROLE_CONFIG = 0x01,
/**
   Analog to fossil's "repository".
*/
FSL_DBROLE_REPO = 0x02,
/**
   Analog to fossil's "localdb".
*/
FSL_DBROLE_CKOUT = 0x04,
/**
   Analog to fossil's "main", which is basically an alias for the
   first db opened. This API opens an in-memory db to act as the main
   db and attaches the repo/checkout/config databases separately so
   that it has complete control over their names and internal
   relationships.
*/
FSL_DBROLE_MAIN = 0x08
};
typedef enum fsl_dbrole_e fsl_dbrole_e;

/**
   Bitmask values specifying "configuration sets."  The values in
   this enum come directly from fossil(1), but they are not part of
   the db structure, so may be changed over time.

   It seems very unlikely that these will ever be used at the level of
   this library. They are a "porting artifact" and retained for the
   time being, but will very likely be removed.
*/
enum fsl_configset_e {
/** Sentinel value. */
FSL_CONFSET_NONE = 0x000000,
/** Style sheet only */
FSL_CONFIGSET_CSS = 0x000001,
/** WWW interface appearance */
FSL_CONFIGSET_SKIN = 0x000002,
/** Ticket configuration */
FSL_CONFIGSET_TKT = 0x000004,
/** Project name */
FSL_CONFIGSET_PROJ = 0x000008,
/** Shun settings */
FSL_CONFIGSET_SHUN = 0x000010,
/** The USER table */
FSL_CONFIGSET_USER = 0x000020,
/** The CONCEALED table */
FSL_CONFIGSET_ADDR = 0x000040,
/** Transfer configuration */
FSL_CONFIGSET_XFER = 0x000080,
/** Everything */
FSL_CONFIGSET_ALL = 0x0000ff,
/** Causes overwrite instead of merge */
FSL_CONFIGSET_OVERWRITE = 0x100000,
/** Use the legacy format */
FSL_CONFIGSET_OLDFORMAT = 0x200000
};
typedef enum fsl_configset_e fsl_configset_e;

/**
   Runtime-configurable flags for a fsl_cx instance.
*/
enum fsl_cx_flags_e {
FSL_CX_F_NONE = 0,
/**
   Tells us whether or not we want to calculate R-cards by default.
   Historically they were initially required but eventually made
   optional due largely to their memory costs.
*/
FSL_CX_F_CALC_R_CARD = 0x01,

/**
   When encounting artifact types in the crosslinking phase which
   the library does not currently support crosslinking for, skip over
   them instead of generating an error.
*/
FSL_CX_F_SKIP_UNKNOWN_CROSSLINKS = 0x02,

/**
   By default, fsl_reserved_fn_check() will fail if the given filename
   is reserved on Windows platforms because such filenames cannot be
   checked out on Windows.  This flag removes that limitation. It
   should only be used, if at all, for repositories which will _never_
   be used on Windows.
 */
FSL_CX_F_ALLOW_WINDOWS_RESERVED_NAMES = 0x04,

/**
   If on (the default) then an internal cache will be used for
   artifact loading to speed up operations which do lots of that.
*/
FSL_CX_F_MANIFEST_CACHE = 0x08,

/**
   Internal use only to prevent duplicate initialization of some
   bits.
*/
FSL_CX_F_IS_OPENING_CKOUT = 0x100,

/**
   Default flags for all fsl_cx instances.
*/
FSL_CX_F_DEFAULTS = FSL_CX_F_MANIFEST_CACHE

};
typedef enum fsl_cx_flags_e fsl_cx_flags_e;

/**
    List of hash policy values. New repositories should generally use
    only SHA3 hashes, but older repos may contain SHA1 hashes (perhaps
    only SHA1), so we have to support those. Repositories may contain
    a mix of hash types.

    ACHTUNG: this enum's values must align with those from fossil(1).
*/
enum fsl_hashpolicy_e {
/* Use only SHA1 hashes. */
FSL_HPOLICY_SHA1 = 0,
/* Accept SHA1 hashes but auto-promote to SHA3. */
FSL_HPOLICY_AUTO = 1,
/* Use SHA3 hashes. */
FSL_HPOLICY_SHA3 = 2,
/* Use SHA3 hashes exclusively. */
FSL_HPOLICY_SHA3_ONLY = 3,
/* With this policy, fsl_uuid_is_shunned() will always return true for
   SHA1 hashes. */
FSL_HPOLICY_SHUN_SHA1 = 4
};
typedef enum fsl_hashpolicy_e fsl_hashpolicy_e;

/**
   Most functions in this API which return an int type return error
   codes from the fsl_rc_e enum.  None of these entries are
   (currently) guaranteed to have a specific value across Fossil
   versions except for FSL_RC_OK, which is guaranteed to always be
   0 (and the API guarantees that no other code shall have a value
   of zero).

   The only reasons numbers are hard-coded to the values (or some of
   them) is to simplify debugging during development. Clients may use
   fsl_rc_cstr() to get some human-readable (or programmer-readable)
   form for any given value in this enum.

   Maintenance reminder: as entries are added/changed, update
   fsl_rc_cstr().
*/
enum fsl_rc_e {
/**
   The quintessential not-an-error value.
*/
FSL_RC_OK = 0,
/**
   Generic/unknown error.
*/
FSL_RC_ERROR = 100,
/**
   A placeholder return value for "not yet implemented" functions.
*/
FSL_RC_NYI = 101,
/**
   Out of memory. Indicates that a resource allocation request
   failed.
*/
FSL_RC_OOM = 102,
/*
  API misuse (invalid args)
*/
FSL_RC_MISUSE = 103,
/**
   Some range was violated (function argument, UTF character, etc.).
*/
FSL_RC_RANGE = 104,
/**
   Indicates that access to or locking of a resource was denied
   by some security mechanism or other.
*/
FSL_RC_ACCESS = 105,
/**
   Indicates an I/O error. Whether it was reading or writing is
   context-dependent.
*/
FSL_RC_IO = 106,
/**
   requested resource not found
*/
FSL_RC_NOT_FOUND = 107,
/**
   Indicates that a to-be-created resource already exists.
*/
FSL_RC_ALREADY_EXISTS = 108,
/**
   Data consistency problem
*/
FSL_RC_CONSISTENCY = 109,

/**
   Indicates that the requested repo needs to be rebuilt.
*/
FSL_RC_REPO_NEEDS_REBUILD = 110,

/**
   Indicates that the requested repo is not, in fact, a repo. Also
   used by some APIs to indicate that they require a repository db
   but none has been opened.
*/
FSL_RC_NOT_A_REPO = 111,

/**
   Indicates an attempt to open a too-old or too-new repository db.
*/
FSL_RC_REPO_VERSION = 112,

/**
   Indicates db-level error (e.g. statement prep failed). In such
   cases, the error state of the related db handle (fsl_db) or
   Fossilc context (fsl_cx) will be updated to contain more
   information directly from the db driver.
*/
FSL_RC_DB = 113,

/**
   Used by some iteration routines to indicate that iteration should
   stop prematurely without an error.
*/
FSL_RC_BREAK = 114,

/**
   Indicates that fsl_stmt_step() has fetched a row and the cursor
   may be used to access the current row state (e.g. using
   fsl_stmt_get_int32() and friends). It is strictly illegal to use
   the fsl_stmt_get_xxx() APIs unless fsl_stmt_step() has returned
   this code.
*/
FSL_RC_STEP_ROW = 115,

/**
   Indicates that fsl_stmt_step() has reached the end of the result
   set and that there is no row data to process. This is also the
   result for non-fetching queries (INSERT and friends). It is strictly
   illegal to use the fsl_stmt_get_xxx() APIs after fsl_stmt_step() has
   returned this code.
*/
FSL_RC_STEP_DONE = 116,

/**
   Indicates that a db-level error occurred during a
   fsl_stmt_step() iteration.
*/
FSL_RC_STEP_ERROR = 117,

/**
   Indicates that some data type or logical type is incorrect
   (e.g. an invalid card type in conjunction with a given
   fsl_deck).
*/
FSL_RC_TYPE = 118,

/**
   Indicates that an operation which requires a checkout does not
   have a checkout to work on.
*/
FSL_RC_NOT_A_CKOUT = 119,

/**
   Indicates that a repo and checkout do not belong together.
*/
FSL_RC_REPO_MISMATCH = 120,
/**
   Indicates that a checksum comparison failed, possibly indicating
   that corrupted or unexpected data was just read.
*/
FSL_RC_CHECKSUM_MISMATCH = 121,

/**
   Indicates that a merge conflict, or some other context-dependent
   type of conflict, was detected.
*/
FSL_RC_CONFLICT,

/**
   This is a special case of FSL_RC_NOT_FOUND, intended specifically
   to differentiate from "file not found in filesystem"
   (FSL_RC_NOT_FOUND) and "fossil does not know about this file" in
   routines for which both might be an error case. An example is a
   an operation which wants to update a repo file with contents
   from the filesystem - the file might not exist or it might not be
   in the current repo db.

   That said, this can also be used for APIs which search for other
   resources (UUIDs, tickets, etc.), but FSL_RC_NOT_FOUND is already
   fairly well entrenched in those cases and is unambiguous, so this
   code is only needed by APIs for which both cases described above
   might happen.
*/
FSL_RC_UNKNOWN_RESOURCE,

/**
   Indicates that a size comparison check failed.

   TODO: remove this if it is not used.
*/
FSL_RC_SIZE_MISMATCH,

/**
   Indicates that an invalid separator was encountered while
   parsing a delta.
*/
FSL_RC_DELTA_INVALID_SEPARATOR,

/**
   Indicates that an invalid size value was encountered while
   parsing a delta.
*/
FSL_RC_DELTA_INVALID_SIZE,

/**
   Indicates that an invalid operator was encountered while parsing
   a delta.
*/
FSL_RC_DELTA_INVALID_OPERATOR,

/**
   Indicates that an invalid terminator was encountered while
   parsing a delta.
*/
FSL_RC_DELTA_INVALID_TERMINATOR,

/**
   Indicates a generic syntax error in a structural artifact. Some
   types of manifest-releated errors are reported with more specific
   error codes, e.g. FSL_RC_RANGE if a given card type appears too
   often.
*/
FSL_RC_SYNTAX,

/**
   Indicates that some value or expression is ambiguous. Typically
   caused by trying to resolve ambiguous symbolic names or hash
   prefixes to their full hashes.
*/
FSL_RC_AMBIGUOUS,

/**
   Used by fsl_checkin_commit(), and similar operations, to indicate
   that they're failing because they would be no-ops. That would
   normally indicate a "non-error," but a condition the caller
   certainly needs to know about.
*/
FSL_RC_NOOP,
/**
   A special case of FSL_RC_NOT_FOUND which indicates that the
   requested repository blob could not be loaded because it is a
   phantom. That is, the record is found but its contents are not
   available. Phantoms are blobs which fossil knows should exist,
   because it's seen references to their hashes, but for which it does
   not yet have any content.
*/
FSL_RC_PHANTOM,

/**
   Indicates that the requested operation is unsupported.
*/
FSL_RC_UNSUPPORTED,

/**
   Indicates that the requested operation is missing certain required
   information.
*/
FSL_RC_MISSING_INFO,

/**
   Must be the final entry in the enum. Used for creating client-side
   result codes which are guaranteed to live outside of this one's
   range.
*/
FSL_RC_end
};
typedef enum fsl_rc_e fsl_rc_e;

/**
   File permissions flags supported by fossil manifests. Their numeric
   values are a hard-coded part of the Fossil architecture and must
   not be changed. Note that these refer to manifest-level permissions
   and not filesystem-level permissions (though they translate to/from
   filesystem-level meanings at some point).
*/
enum fsl_fileperm_e {
/** Indicates a regular, writable file. */
FSL_FILE_PERM_REGULAR = 0,
/** Indicates a regular file with the executable bit set. */
FSL_FILE_PERM_EXE = 0x1,
/**
   Indicates a symlink. Note that symlinks do not have the executable
   bit set separately on Unix systems. Also note that libfossil does
   NOT YET IMPLEMENT symlink support like fossil(1) does - it
   currently treats symlinks (mostly) as Unix treats symlinks.
*/
FSL_FILE_PERM_LINK = 0x2
};
typedef enum fsl_fileperm_e fsl_fileperm_e;

/**
   Returns a "standard" string form for a fsl_rc_e code.  The string
   is primarily intended for debugging purposes.  The returned bytes
   are guaranteed to be static and NUL-terminated. They are not
   guaranteed to contain anything useful for any purposes other than
   debugging and tracking down problems.
*/
FSL_EXPORT char const * fsl_rc_cstr(int);

/**
   Returns the value of FSL_LIBRARY_VERSION used to compile the
   library. If this value differs from the value the caller was
   compiled with, Chaos might ensue.

   The API does not yet have any mechanism for determining
   compatibility between repository versions and it also currently
   does no explicit checking to disallow incompatible versions.
*/
FSL_EXPORT char const * fsl_library_version();

/**
   Returns true (non-0) if yourLibVersion compares lexically
   equal to FSL_LIBRARY_VERSION, else it returns false (0).
*/
FSL_EXPORT bool fsl_library_version_matches(char const * yourLibVersion);

/**
   This type, accessible to clients via the ::fsl_lib_configurable
   global, contains configuration-related data for the library
   which can be swapped out by clients.
*/
struct fsl_lib_configurable_t {
  /**
     Library-wide allocator. It may be replaced by the client IFF
     it is replaced before the library allocates any memory. The
     default implementation uses the C-standard
     de/re/allocators. Modifying this member while any memory
     allocated through it is still "live" leads to undefined
     results. There is an exception: a "read-only" middleman proxy
     which does not change how the memory is allocated or
     intepreted can safely be swapped in or out at any time
     provided the underlying allocator stays the same and the
     client can ensure that there are no thread-related race
     conditions. e.g. it is legal to swap this out with a proxy
     which logs allocation requests and then forwards the call on
     to the original implementation, and it is legal to do so at
     essentially any time. The important thing this that all of the
     library-allocated memory goes through a single underlying
     (de)allocator for the lifetime of the application.
  */
  fsl_allocator allocator;
};
typedef struct fsl_lib_configurable_t fsl_lib_configurable_t;
FSL_EXPORT fsl_lib_configurable_t fsl_lib_configurable;

/**
   A part of the configuration used by fsl_cx_init() and friends.

*/
struct fsl_cx_config {
  /**
     If true, all SQL which goes through the fossil engine
     will be traced to the fsl_output()-configured channel.
  */
  int traceSql;
  /**
     If true, the fsl_print() SQL function will output its output to the
     fsl_output()-configured channel, else it is a no-op.
  */
  int sqlPrint;

  /**
     Specifies the default hash policy.
  */
  fsl_hashpolicy_e hashPolicy;
};

/**
   fsl_cx_config instance initialized with defaults, intended for
   in-struct initialization.
*/
#define fsl_cx_config_empty_m {                 \
    0/*traceSql*/,                              \
    0/*sqlPrint*/,                            \
    FSL_HPOLICY_SHA3/*hashPolicy*/ \
}

/**
   fsl_cx_config instance initialized with defaults, intended for
   copy-initialization.
*/
FSL_EXPORT const fsl_cx_config fsl_cx_config_empty;

/**
   Parameters for fsl_cx_init().
*/
struct fsl_cx_init_opt {
  /**
     The output channel for the Fossil instance.
  */
  fsl_outputer output;
  /**
     Basic configuration parameters.
  */
  fsl_cx_config config;
};


/** Empty-initialized fsl_cx_init_opt instance. */
#define fsl_cx_init_opt_empty_m {fsl_outputer_empty_m, fsl_cx_config_empty_m}
/**
   fsl_cx_init_opt instance initialized to use stdout for output and
   the standard system memory allocator.
*/
#define fsl_cx_init_opt_default_m {fsl_outputer_FILE_m, fsl_cx_config_empty_m}

/** Empty-initialized fsl_cx_init_opt instance. */
FSL_EXPORT const fsl_cx_init_opt fsl_cx_init_opt_empty;

/**
   fsl_cx_init_opt instance initialized to use stdout for output and
   the standard system memory allocator. Used as the default when
   fsl_cx_init() is passed a NULL value for this parameter.
*/
FSL_EXPORT const fsl_cx_init_opt fsl_cx_init_opt_default;

/**
   Allocates a new fsl_cx instance, which must eventually
   be passed to fsl_cx_finalize() to clean it up.
   Normally clients do not need this - they can simply pass
   a pointer to NULL as the first argument to fsl_cx_init()
   to let it allocate an instance for them.
*/
FSL_EXPORT fsl_cx * fsl_cx_malloc();

/**
   Initializes a fsl_cx instance. tgt must be a pointer to NULL,
   e.g.:

   @code
   fsl_cxt * f = NULL; // NULL is important - see below
   int rc = fsl_cx_init( &f, NULL );
   @endcode

   It is very important that f be initialized to NULL _or_ to an
   instance which has been properly allocated and empty-initialized
   (e.g. via fsl_cx_malloc()). If *tgt is NULL, this routine
   allocates the context, else it assumes the caller did. If f
   points to unitialized memory then results are undefined.

   If the second parameter is NULL then default implementations are
   used for the context's output routine and other options. If it
   is not NULL then param->allocator and param->output must be
   initialized properly before calling this function. The contents
   of param are bitwise copied by this function and ownership of
   the returned value is transfered to *tgt in all cases except
   one:

   If passed a pointer to a NULL context and this function cannot
   allocate it, it returns FSL_RC_OOM and does not modify *tgt. In
   this one case, ownership of the context is not changed (as there's
   nothing to change!). On any other result (including errors),
   ownership of param's contents are transfered to *tgt and the client
   is responsible for passing *tgt ot fsl_cxt_finalize() when he is
   done with it. Note that (like in sqlite3), *tgt may be valid memory
   even if this function fails, and the caller must pass it to
   fsl_cx_finalize() whether or not this function succeeds unless it
   fails at the initial OOM (which the client can check by seeing if
   (*tgt) is NULL, but only if he set it to NULL before calling this).

   Returns 0 on success, FSL_RC_OOM on an allocation error,
   FSL_RC_MISUSE if (!tgt). If this function fails, it is illegal to
   use the context object except to pass it to fsl_cx_finalize(), as
   explained above.

   @see fsl_cx_finalize()
   @see fsl_cx_reset()
*/
FSL_EXPORT int fsl_cx_init( fsl_cx ** tgt, fsl_cx_init_opt const * param );

/**
   Clears (most) dynamic state in f, but does not free f and does
   not free "static" state (that set up by the init process). If
   closeDatabases is true then any databases managed by f are
   closed, else they are kept open.

   Client code will not normally need this - it is intended for a
   particular potential memory optimization case. If (and only if)
   closeDatabases is true then after calling this, f may be legally
   re-used as a target for fsl_cx_init().

   This function does not trigger any finializers set for f's
   client state or output channel. It _does_ clear any user name
   set via fsl_cx_user_set().

   Results are undefined if !f or f's memory has not been properly
   initialized.
*/
FSL_EXPORT void fsl_cx_reset( fsl_cx * f, bool closeDatabases );

/**
   Frees all memory associated with f, which must have been
   allocated/initialized using fsl_cx_malloc(), fsl_cx_init(), or
   equivalent, or created on the stack and properly initialized
   (via fsl_cx_init() or copy-constructed from fsl_cx_empty).

   This function triggers any finializers set for f's client state
   or output channel.

   This is a no-op if !f and is effectively a no-op if f has no
   state to destruct.
*/
FSL_EXPORT void fsl_cx_finalize( fsl_cx * f );


/**
   Sets or unsets one or more option flags on the given fossil
   context.  flags is the flag or a bitmask of flags to set (from
   the fsl_cx_flags_e enum).  If enable is true the flag(s) is (are)
   set, else it (they) is (are) unset. Returns the new set of
   flags.
*/
FSL_EXPORT int fsl_cx_flag_set( fsl_cx * f, int flags, bool enable );

/**
   Returns f's flags.
*/
FSL_EXPORT int fsl_cx_flags_get( fsl_cx * f );

/**
   Sets the Fossil error state to the given error code and
   fsl_appendf()-style format string/arguments. On success it
   returns the code parameter. It does not return 0 unless code is
   0, and if it returns a value other than code then something went
   seriously wrong (e.g. allocation error: FSL_RC_OOM) or the
   arguments were invalid: !f results in FSL_RC_MISUSE.

   If !fmt then fsl_rc_cstr(code) is used to create the
   error string.

   As a special case, if code is FSL_RC_OOM, no error string is
   allocated (because it would likely fail, assuming the OOM
   is real).

   As a special case, if code is 0 (the non-error value) then fmt is
   ignored and any error state is cleared.
*/
FSL_EXPORT int fsl_cx_err_set( fsl_cx * f, int code, char const * fmt, ... );

/**
   va_list counterpart to fsl_cx_err_set().
*/
FSL_EXPORT int fsl_cx_err_setv( fsl_cx * f, int code, char const * fmt,
                                va_list args );

/**
   Fetches the error state from f. See fsl_error_get() for the semantics
   of the parameters and return value.
*/
FSL_EXPORT int fsl_cx_err_get( fsl_cx * f, char const ** str, fsl_size_t * len );

/**
   Returns f's error state object. This pointer is guaranteed by the
   API to be stable until f is finalized, but its contents are
   modified my routines as part of the error reporting process.

   Returns NULL if !f.
*/
FSL_EXPORT fsl_error const * fsl_cx_err_get_e(fsl_cx const * f);

/**
   Resets's f's error state, basically equivalent to
   fsl_cx_err_set(f,0,NULL). Is a no-op if f is NULL.  This may be
   necessary for apps if they rely on looking at fsl_cx_err_get()
   at the end of their app/routine, because error state survives
   until it is cleared, even if the error held there was caught and
   recovered. This function might keep error string memory around
   for re-use later on.
*/
FSL_EXPORT void fsl_cx_err_reset(fsl_cx * f);

/**
   Replaces f's error state with the contents of err, taking over
   any memory owned by err (but not err itself). Returns the new
   error state code (the value of err->code before this call) on
   success. The only error case is if !f (FSL_RC_MISUSE). If err is
   NULL then f's error state is cleared and 0 is returned. err's
   error state is cleared by this call.
*/
FSL_EXPORT int fsl_cx_err_set_e( fsl_cx * f, fsl_error * err );

/**
   If f has error state then it outputs its error state to its
   output channel and returns the result of fsl_output(). Returns
   FSL_RC_MISUSE if !f, 0 if f has no error state our output of the
   state succeeds. If addNewline is true then it adds a trailing
   newline to the output, else it does not.

   This is intended for testing and debugging only, and not as an
   error reporting mechanism for a full-fledged application.
*/
FSL_EXPORT int fsl_cx_err_report( fsl_cx * f, char addNewline );

/**
   Unconditionally Moves db->error's state into f. If db is NULL then
   f's primary db connection is used. Returns FSL_RC_MISUSE if !f or
   (!db && f-is-not-opened). On success it returns f's new error code.

   The main purpose of this function is to propagate db-level
   errors up to higher-level code which deals directly with the f
   object but not the underlying db(s).

   @see fsl_cx_uplift_db_error2()
*/
FSL_EXPORT int fsl_cx_uplift_db_error( fsl_cx * f, fsl_db * db );

/**
   If rc is not 0 and f has no error state but db does, this calls
   fsl_cx_uplift_db_error() and returns its result, else returns
   rc. If db is NULL, f's main db connection is used. It is intended
   to be called immediately after calling a db operation which might
   have failed, and passed that operation's result.

   Results are undefined if db is NULL and f has no main db
   connection.
*/
FSL_EXPORT int fsl_cx_uplift_db_error2(fsl_cx *f, fsl_db * db, int rc);

/**
   Outputs the first n bytes of src to f's configured output
   channel. Returns 0 on success, FSL_RC_MISUSE if (!f || !src),
   0 (without side effects) if !n, else it returns the result of
   the underlying output call. This is a harmless no-op if f is
   configured with no output channel.

   @see fsl_outputf()
   @see fsl_flush()
*/
FSL_EXPORT int fsl_output( fsl_cx * f, void const * src, fsl_size_t n );

/**
   Flushes f's output channel. Returns 0 on success, FSL_RC_MISUSE
   if !f. If the flush routine is NULL then this is a harmless
   no-op.

   @see fsl_outputf()
   @see fsl_output()
*/
FSL_EXPORT int fsl_flush( fsl_cx * f );

/**
   Uses fsl_appendf() to append formatted output to the channel
   configured for use with fsl_output(). Returns 0 on success,
   FSL_RC_MISUSE if !f or !fmt, FSL_RC_RANGE if !*fmt, and
   FSL_RC_IO if the underlying fsl_appendf() operation fails.

   Note, however, that due to the printf()-style return semantics
   of fsl_appendf(), it is not generically possible to distinguish
   a partially-successful (i.e. failed in the middle) write from
   success. e.g. if fmt contains a format specifier which performs
   memory allocation and that allocation fails, it is unlikely that
   this function will be able to be aware of that error. The only
   way to fix that is to change the return semantics of
   fsl_appendf() (and adjust any existing code which relies on
   them).

   @see fsl_output()
   @see fsl_flush()
*/
FSL_EXPORT int fsl_outputf( fsl_cx * f, char const * fmt, ... );

/**
   va_list counterpart to fsl_outputf().
*/
FSL_EXPORT int fsl_outputfv( fsl_cx * f, char const * fmt, va_list args );

/**
   Opens the given db file name as f's repository. Returns 0 on
   success. On error it sets f's error state and returns that code
   unless the error was FSL_RC_MISUSE (which indicates invalid
   arguments and it does not set the error state).

   Fails with FSL_RC_MISUSE if !f, !repoDbFile, !*repoDbFile. Returns
   FSL_RC_ACCESS if f already has an opened repo db.

   Returns FSL_RC_NOT_FOUND if repoDbFile is not found, as this
   routine cannot create a new repository db.

   When a repository is opened, the fossil-level user name
   associated with f (if any) is overwritten with the default user
   from the repo's login table (the one with uid=1). Thus
   fsl_cx_user_get() may return a value even if the client has not
   called fsl_cx_user_set().

   It would be nice to have a parameter specifying that the repo
   should be opened read-only. That's not as straightforward as it
   sounds because of how the various dbs are internally managed
   (via one db handle). Until then, the permissions of the
   underlying repo file will determine how it is opened. i.e. a
   read-only repo will be opened read-only.


   Potentially interesting side-effects:

   - On success this re-sets several bits of f's configuration to
   match the repository-side settings.

   @see fsl_repo_create()
   @see fsl_repo_close()
*/
FSL_EXPORT int fsl_repo_open( fsl_cx * f, char const * repoDbFile/*, char readOnlyCurrentlyIgnored*/ );

/**
   If fsl_repo_open_xxx() or fsl_ckout_open_dir() has been used to
   open a respository db, this call closes that db and returns
   0. Returns FSL_RC_MISUSE if f has any transactions pending,
   FSL_RC_NOT_FOUND if f has not opened a repository.

   If a repository is opened "indirectly" via fsl_ckout_open_dir()
   then attempting to close it using this function will result in
   FSL_RC_MISUSE and f's error state will hold a description of the
   problem. Such a repository will be closed implicitly when the
   checkout db is closed.

   @see fsl_repo_open()
   @see fsl_repo_create()
*/
FSL_EXPORT int fsl_repo_close( fsl_cx * f );

/**
   Sets or clears (if userName is NULL or empty) the default
   repository user name for operations which require one.

   Returns 0 on success, FSL_RC_MISUSE if f is NULL,
   FSL_RC_OOM if copying of the userName fails.

   Example usage:
   @code
   char * u = fsl_guess_user_name();
   int rc = fsl_cx_user_set(f, u);
   fsl_free(u);
   @endcode

   (Sorry about the extra string copy there, but adding a function
   which passes ownership of the name string seems like overkill.)
*/
FSL_EXPORT int fsl_cx_user_set( fsl_cx * f, char const * userName );

/**
   Returns the name set by fsl_cx_user_set(), or NULL if f has no
   default user name set. The returned bytes are owned by f and may
   be invalidated by any call to fsl_cx_user_set().
*/
FSL_EXPORT char const * fsl_cx_user_get( fsl_cx const * f );

/**
   Configuration parameters for fsl_repo_create().  Always
   copy-construct these from fsl_repo_create_opt_empty
   resp. fsl_repo_create_opt_empty_m in order to ensure proper
   behaviour vis-a-vis default values.

   TODOs:

   - Add project name/description, and possibly other
   configuration bits.

   - Allow client to set password for default user (currently set
   randomly, as fossil(1) does).
*/
struct fsl_repo_create_opt {
  /**
     The file name for the new repository.
  */
  char const * filename;
  /**
     Fossil user name for the admin user in the new repo.  If NULL,
     defaults to the Fossil context's user (see
     fsl_cx_user_get()). If that is NULL, it defaults to
     "root" for historical reasons.
  */
  char const * username;

  /**
     The comment text used for the initial commit. If NULL or empty
     (starts with a NUL byte) then no initial check is
     created. fossil(1) is largely untested with that scenario (but
     it seems to work), so for compatibility it is not recommended
     that this be set to NULL.

     The default value (when copy-initialized) is "egg". There's a
     story behind the use of "egg" as the initial checkin comment,
     and it all started with a typo: "initial chicken"
  */
  char const * commitMessage;

  /**
     Mime type for the commit message (manifest N-card). Manifests
     support this but fossil(1) has never (as of 2021-02) made use of
     it. It is provided for completeness but should, for
     compatibility's sake, probably not be set, as the fossil UI may
     not honor it. The implied default is text/x-fossil-wiki. Other
     ostensibly legal values include text/plain and text/x-markdown.
     This API will accept any value, but results are technically
     undefined with any values other than those listed above.
  */
  char const * commitMessageMimetype;

  /**
     If not NULL and not empty, fsl_repo_create() will use this
     repository database to copy the configuration, copying over
     the following settings:

     - The reportfmt table, overwriting any existing entries.

     - The user table fields (cap, info, mtime, photo) are copied
     for the "system users".  The system users are: anonymous,
     nobody, developer, reader.

     - The vast majority of the config table is copied, arguably
     more than it should (e.g. the 'manifest' setting).
  */
  char const * configRepo;

  /**
     If false, fsl_repo_create() will fail if this->filename
     already exists.
  */
  bool allowOverwrite;
  
};
typedef struct fsl_repo_create_opt fsl_repo_create_opt;

/** Initialized-with-defaults fsl_repo_create_opt struct, intended
    for in-struct initialization. */
#define fsl_repo_create_opt_empty_m {           \
    NULL/*filename*/,                           \
    NULL/*username*/,                         \
    "egg"/*commitMessage*/,                   \
    NULL/*commitMessageMimetype*/,            \
    NULL/*configRepo*/,                       \
    false/*allowOverwrite*/                     \
    }

/** Initialized-with-defaults fsl_repo_create_opt struct, intended
    for copy-initialization. */
FSL_EXPORT const fsl_repo_create_opt fsl_repo_create_opt_empty;

/**
   Creates a new repository database using the options provided in the
   second argument. If f is not NULL, it must be a valid context
   instance, though it need not have an opened checkout/repository. If
   f has an opened repo or checkout, this routine closes them but that
   closing _will fail_ if a transaction is currently active!

   If f is NULL, a temporary context is used for creating the
   repository, in which case the caller will not have access to
   detailed error information (only the result code) if this operation
   fails. In that case, the resulting repository file will, on
   success, be found at the location referred to by opt.filename.

   The opt argument may not be NULL.

   If opt->allowOverwrite is false (0) and the file exists, it fails
   with FSL_RC_ALREADY_EXISTS, otherwise is creates/overwrites the
   file. This is a destructive operation if opt->allowOverwrite is
   true, so be careful: the existing database will be truncated and
   re-created.

   This operation installs the various "static" repository schemas
   into the db, sets up some default settings, and installs a
   default user.

   This operation always closes any repository/checkout opened by f
   because setting up the new db requires wiring it to f to set up
   some of the db-side infrastructure. The one exception is if
   argument validation fails, in which case f's repo/checkout-related
   state are not modified. Note that closing will fail if a
   transaction is currently active and that, in turn, will cause this
   operation to fail.

   See the fsl_repo_create_opt docs for more details regarding the
   creation options.

   On success, 0 is returned and f (if not NULL) is left with the
   new repository opened and ready for use. On error, f's error
   state is updated and any number of the FSL_RC_xxx codes may be
   returned - there are no less than 30 different _potential_ error
   conditions on the way to creating a new repository.

   If initialization of the repository fails, this routine will
   attempt to remove its partially-initialize corpse from the
   filesystem but will ignore any errors encountered while doing so.

   Example usage:

   @code
   fsl_repo_create_opt opt = fsl_repo_create_opt_empty;
   int rc;
   opt.filename = "my.fossil";
   // ... any other opt.xxx you want to set, e.g.:
   // opt.user = "fred";
   // Assume fsl is a valid fsl_cx instance:
   rc = fsl_repo_create(fsl, &opt );
   if(rc) { ...error... }
   else {
     fsl_db * db = fsl_cx_db_repo(f);
     assert(db); // == the new repo db
   ...
   }
   @endcode

   @see fsl_repo_open()
   @see fsl_repo_close()
*/
FSL_EXPORT int fsl_repo_create(fsl_cx * f, fsl_repo_create_opt const * opt );

/**
   UNTESTED.

   Returns true if f has an opened repository database which is
   opened in read-only mode, else returns false.
*/
FSL_EXPORT char fsl_repo_is_readonly(fsl_cx const * f);

/**
   Tries to open a checked-out fossil repository db in the given
   directory. The (dirName, checkParentDirs) parameters are passed on
   as-is to fsl_ckout_db_search() to find a checkout db, so see that
   routine for how it searches.

   If this routine finds/opens a checkout, it also tries to open
   the repository database from which the checkout derives (and
   fails if it cannot).

   Returns 0 on success. If there is an error opening or validating
   the checkout or its repository db, f's error state will be
   updated. Error codes/conditions include:

   - FSL_RC_MISUSE if f is NULL.

   - FSL_RC_ACCESS if f already has and opened checkout.

   - FSL_RC_OOM if an allocation fails.

   - FSL_RC_NOT_FOUND if no checkout is foud or if a checkout's
   repository is not found.

   - FSL_RC_RANGE if dirname is not NULL but has a length of 0,
   either because 0 was passed in for dirNameLen or because
   dirNameLen was negative and *dirName is a NUL byte.

   - Various codes from fsl_getcwd() (if dirName is NULL).

   - Various codes if opening the associated repository DB fails.

   TODO: there's really nothing in the architecture which restricts
   a checkout db to being in the same directory as the checkout,
   except for some historical bits which "could" be refactored. It
   "might be interesting" to eventually provide a variant which
   opens a checkout db file directly. We have the infrastructure,
   just need some refactoring. We would need to add the working
   directory path to the checkout db's config, but should otherwise
   require no trickery or incompatibilities with fossil(1).
*/
FSL_EXPORT int fsl_ckout_open_dir( fsl_cx * f, char const * dirName,
                                   bool checkParentDirs );


/**
   Searches the given directory (or the current directory if dirName
   is 0) for a fossil checkout database file named one of (_FOSSIL_,
   .fslckout).  If it finds one, it returns 0 and appends the file's
   path to pOut if pOut is not 0.  If neither is found AND if
   checkParentDirs is true (non-0) an then it moves up the path one
   directory and tries again, until it hits the root of the dirPath
   (see below for a note/caveat).

   If dirName is NULL then it behaves as if it had been passed the
   absolute path of the current directory (as determined by
   fsl_getcwd()).

   This function does no normalization of dirName. Because of that...

   Achtung: if dirName is relative, this routine might not find a
   checkout where it would find one if given an absolute path (because
   it traverses the path string given it instead of its canonical
   form). Wether this is a bug or a feature is not yet clear. When in
   doubt, use fsl_file_canonical_name() to normalize the directory
   name before passing it in here. If it turns out that we always want
   that behaviour, this routine will be modified to canonicalize the
   name.

   This routine can return at least the following error codes:

   - FSL_RC_NOT_FOUND: either no checkout db was found or the given
   directory was not found.

   - FSL_RC_RANGE if dirName is an empty string. (We could arguably
   interpret this as a NULL string, i.e. the current directory.)

   - FSL_RC_OOM if allocation of a filename buffer fails.

*/
FSL_EXPORT int fsl_ckout_db_search( char const * dirName,
                                    bool checkParentDirs,
                                    fsl_buffer * pOut );


/**
   If fsl_ckout_open_dir() (or similar) has been used to open a
   checkout db, this call closes that db and returns 0. Returns
   FSL_RC_MISUSE if f has any transactions pending, FSL_RC_NOT_FOUND
   if f has not opened a checkout (which can safely be ignored and
   does not update f's error state).

   This also closes the repository which was implicitly opened for the
   checkout.
*/
FSL_EXPORT int fsl_ckout_close( fsl_cx * f );

/**
   Attempts to Closes any opened databases (repo/checkout/config). This will
   fail if any transactions are pending. Any databases which are already
   closed are silently skipped.
*/
FSL_EXPORT int fsl_cx_close_dbs( fsl_cx * f );

/**
   If f is not NULL and has a checkout db opened then this function
   returns its name. The bytes are valid until that checkout db
   connection is closed. If len is not NULL then *len is (on
   success) assigned to the length of the returned string, in
   bytes. The string is NUL-terminated, so fetching the length (by
   passing a non-NULL 2nd parameter) is optional.

   Returns NULL if !f or f has no checkout opened.

   @see fsl_ckout_open_dir()
   @see fsl_cx_ckout_dir_name()
   @see fsl_cx_db_file_config()
   @see fsl_cx_db_file_repo()
*/
FSL_EXPORT char const * fsl_cx_db_file_ckout(fsl_cx const * f,
                                             fsl_size_t * len);

/**
   Equivalent to fsl_ckout_db_file() except that
   it applies to the name of the opened repository db,
   if any.

   @see fsl_cx_db_file_ckout()
   @see fsl_cx_db_file_config()
*/
FSL_EXPORT char const * fsl_cx_db_file_repo(fsl_cx const * f,
                                            fsl_size_t * len);

/**
   Equivalent to fsl_ckout_db_file() except that
   it applies to the name of the opened config db,
   if any.

   @see fsl_cx_db_file_ckout()
   @see fsl_cx_db_file_repo()
*/
FSL_EXPORT char const * fsl_cx_db_file_config(fsl_cx const * f,
                                              fsl_size_t * len);

/**
   Similar to fsl_cx_db_file_ckout() and friends except that it
   applies to db file implied by the specified role (2nd
   parameter). If no such role is opened, or the role is invalid,
   NULL is returned.
*/
FSL_EXPORT char const * fsl_cx_db_file_for_role(fsl_cx const * f,
                                                fsl_dbrole_e r,
                                                fsl_size_t * len);

/**
   Similar to fsl_cx_db_file_ckout() and friends except that it
   applies to DB name (as opposed to DB _file_ name) implied by the
   specified role (2nd parameter). If no such role is opened, or the
   role is invalid, NULL is returned.

   If the 3rd argument is not NULL, it is set to the length, in bytes,
   of the returned string. The returned strings are static and
   NUL-terminated.

   This is the "easiest" way to figure out the DB name of the given
   role, independent of what order f's databases were opened
   (because the first-opened DB is always called "main").

   The Fossil-standard names of its primary databases are: "localdb"
   (checkout), "repository", and "configdb" (global config DB), but
   libfossil uses "ckout", "repo", and "cfg", respective. So long as
   queries use table names which unambiguously refer to a given
   database, the DB name is normally not needed. It is needed when
   creating new non-TEMP db tables and views. By default such
   tables/views would go into the "main" DB, which is actually a
   transient DB in this API, so it's important to use the correct DB
   name when creating such constructs.
*/
FSL_EXPORT char const * fsl_cx_db_name_for_role(fsl_cx const * f,
                                                fsl_dbrole_e r,
                                                fsl_size_t * len);

/**
   If f has an opened checkout db (from fsl_ckout_open_dir()) then
   this function returns the directory part of the path for the
   checkout, including (for historical and internal convenience
   reasons) a trailing slash. The returned bytes are valid until that
   db connection is closed. If len is not NULL then *len is (on
   success) assigned to the length of the returned string, in bytes.
   The string is NUL-terminated, so fetching the length by passing a
   non-NULL 2nd parameter is optional.

   Returns NULL if !f or f has no checkout opened.

   @see fsl_ckout_open_dir()
   @see fsl_ckout_db_file()
*/
FSL_EXPORT char const * fsl_cx_ckout_dir_name(fsl_cx const * f,
                                              fsl_size_t * len);

/**
   Returns a handle to f's main db (which may or may not have any
   relationship to the repo/checkout/config databases - that's
   unspecified!), or NULL if !f. The returned object is owned by f and
   the client MUST NOT do any of the following:

   - Close the db handle.

   - Use transactions without using fsl_db_transaction_begin()
   and friends.

   - Fiddle with the handle's internals. Doing so might confuse its
   owning context.

   Clients MAY add new user-defined functions, use the handle with
   fsl_db_prepare(), and other "mundane" db-related tasks.

   Design notes:

   The current architecture uses an in-memory db as the "main" db and
   attaches the repo, checkout, and config dbs using well-defined
   names. Even so, it uses separate fsl_db instances to track each one
   so that we "could," if needed, switch back to a multi-db-instance
   approach if needed.

   @see fsl_cx_db_repo()
   @see fsl_cx_db_ckout()
*/
FSL_EXPORT fsl_db * fsl_cx_db( fsl_cx * f );

/**
   If f is not NULL and has had its repo opened via
   fsl_repo_open(), fsl_ckout_open_dir(), or similar, this
   returns a pointer to that database, else it returns NULL.

   @see fsl_cx_db()
*/
FSL_EXPORT fsl_db * fsl_cx_db_repo( fsl_cx * f );

/**
   If f is not NULL and has had a checkout opened via
   fsl_ckout_open_dir() or similar, this returns a pointer to that
   database, else it returns NULL.

   @see fsl_cx_db()
*/
FSL_EXPORT fsl_db * fsl_cx_db_ckout( fsl_cx * f );

/**
   A helper which fetches f's repository db. If f has no repo db
   then it sets f's error state to FSL_RC_NOT_A_REPO with a message
   describing the requirement, then returns NULL.  Returns NULL if
   !f.

   @see fsl_cx_db()
   @see fsl_cx_db_repo()
   @see fsl_needs_ckout()
*/
FSL_EXPORT fsl_db * fsl_needs_repo(fsl_cx * f);

/**
   The checkout-db counterpart of fsl_needs_repo().

   @see fsl_cx_db()
   @see fsl_needs_repo()
   @see fsl_cx_db_ckout()
*/
FSL_EXPORT fsl_db * fsl_needs_ckout(fsl_cx * f);

/**
   Opens the given database file as f's configuration database. If f
   already has a config database opened, it is closed before opening
   the new one. The database is created and populated with an
   initial schema if needed.

   If dbName is NULL or empty then it uses a default db name,
   "probably" under the user's home directory. To get the name of
   the database after it has been opened/attached, use
   fsl_cx_db_file_config().

   TODO: strongly consider supporting non-attached
   (i.e. sqlite3_open()'d) use of the config db. Comments in fossil(1)
   suggest that it is possible to lock the config db for other apps
   when it is attached to a long-running op by a fossil process.

   @see fsl_cx_db_config()
   @see fsl_config_close()
*/
FSL_EXPORT int fsl_config_open( fsl_cx * f, char const * dbName );

/**
   Closes/detaches the database connection opened by
   fsl_config_open(). Returns 0 on succes, FSL_RC_MISUSE if !f,
   FSL_RC_NOT_FOUND if no config db connection is opened/attached.

   @see fsl_cx_db_config()
   @see fsl_config_open()
*/
FSL_EXPORT int fsl_config_close( fsl_cx * f );

/**
   If f has an opened/attached configuration db then its handle is
   returned, else 0 is returned.

   @see fsl_config_open()
   @see fsl_config_close()
*/
FSL_EXPORT fsl_db * fsl_cx_db_config( fsl_cx * f );

/**
   Convenience form of fsl_db_prepare() which uses f's main db.
   Returns 0 on success, FSL_RC_MISUSE if !f or !sql, FSL_RC_RANGE
   if !*sql.
*/
FSL_EXPORT int fsl_cx_prepare( fsl_cx *f, fsl_stmt * tgt, char const * sql, ... );

/**
   va_list counterpart of fsl_cx_prepare().
*/
FSL_EXPORT int fsl_cx_preparev( fsl_cx *f, fsl_stmt * tgt, char const * sql, va_list args );

/**
   Wrapper around fsl_db_last_insert_id() which uses f's main
   database. Returns -1 if !f or f has no opened db.

   @see fsl_cx_db()
*/
FSL_EXPORT fsl_id_t fsl_cx_last_insert_id(fsl_cx *f);


/**
   Works similarly to fsl_stat(), except that zName must refer to a
   path under f's current checkout directory. Note that this stats
   local files, not repository-level content.

   If relativeToCwd is true then the filename is
   resolved/canonicalized based on the current working directory (see
   fsl_getcwd()), otherwise f's current checkout directory is used as
   the virtual root. This makes a subtle yet important difference in
   how the name is resolved. Applications taking input from users
   (e.g. CLI apps) will normally want to resolve from the current
   working dir (assuming the filenames were passed in from the
   CLI). In a GUI environment, where the current directory is likely
   not the checkout root, resolving based on the checkout root
   (i.e. relativeToCwd=false) is probably saner.

   Returns 0 on success. Errors include, but are not limited to:

   - FSL_RC_MISUSE if !zName.

   - FSL_RC_NOT_A_CKOUT if f has no opened checkout.

   - If fsl_is_simple_pathname(zName) returns false then
   fsl_ckout_filename_check() is used to normalize the name. If
   that fails, its failure code is returned.

   - As for fsl_stat().

   See fsl_stat() for more details regarding the tgt parameter.

   TODO: fossil-specific symlink support. Currently it does not
   distinguish between symlinks and non-links.

   @see fsl_cx_stat2()
*/
FSL_EXPORT int fsl_cx_stat( fsl_cx * f, bool relativeToCwd,
                            char const * zName, fsl_fstat * tgt );

/**
   This works identically to fsl_cx_stat(), but provides more
   information about the file being stat'd.

   If nameOut is not NULL then the resolved/normalized path to to
   that file is appended to nameOut. If fullPath is true then an
   absolute path is written to nameOut, otherwise a
   checkout-relative path is written.

   Returns 0 on success. On stat() error, nameOut is not updated,
   but after stat()'ing, allocation of memory for nameOut's buffer
   may fail.

   If zName ends with a trailing slash, that slash is retained in
   nameOut.

   This function DOES NOT resolve symlinks, stat()nig the link instead
   of what it points to.

   @see fsl_cx_stat()
*/
FSL_EXPORT int fsl_cx_stat2( fsl_cx * f, bool relativeToCwd, char const * zName,
                             fsl_fstat * tgt, fsl_buffer * nameOut, bool fullPath);


/**
   Sets the case-sensitivity flag for f to the given value. This
   flag alters how some filename-search/comparison operations
   operate. This option is only intended to have an effect on
   plaforms with case-insensitive filesystems.

   @see fsl_cx_is_case_sensitive()
*/
FSL_EXPORT void fsl_cx_case_sensitive_set(fsl_cx * f, bool caseSensitive);

/**
   Returns true (non-0) if f is set for case-sensitive filename
   handling, else 0. Returns 0 if !f.

   @see fsl_cx_case_sensitive_set()
*/
FSL_EXPORT bool fsl_cx_is_case_sensitive(fsl_cx const * f);

/**
   If f is set to use case-sensitive filename handling,
   returns a pointer to an empty string, otherwise a pointer
   to the string "COLLATE nocase" is returned.
   Results are undefined if f is NULL. The returned bytes
   are static. 

   @see fsl_cx_case_sensitive_set()
   @see fsl_cx_is_case_sensitive()
*/
FSL_EXPORT char const * fsl_cx_filename_collation(fsl_cx const * f);

/**
   An enumeration of the types of structural artifacts used by
   Fossil. The numeric values of all entries before FSL_SATYPE_count,
   with the exception of FSL_SATYPE_INVALID, are a hard-coded part of
   the Fossil db architecture and must never be changed. Any after
   FSL_SATYPE_count are libfossil extensions.
*/
enum fsl_satype_e {
/**
   Sentinel value used for some error reporting.
*/
FSL_SATYPE_INVALID = -1,
/**
   Sentinel value used to mark a deck as being "any" type. This is
   a placeholder on a deck's way to completion.
*/
FSL_SATYPE_ANY = 0,
/**
   Indicates a "manifest" artifact (a checkin record).
*/
FSL_SATYPE_CHECKIN = 1,
/**
   Indicates a "cluster" artifact. These are used during synchronization.
*/
FSL_SATYPE_CLUSTER = 2,
/**
   Indicates a "control" artifact (a tag change).
*/
FSL_SATYPE_CONTROL = 3,
/**
   Indicates a "wiki" artifact.
*/
FSL_SATYPE_WIKI = 4,
/**
   Indicates a "ticket" artifact.
*/
FSL_SATYPE_TICKET = 5,
/**
   Indicates an "attachment" artifact (used in the ticketing
   subsystem).
*/
FSL_SATYPE_ATTACHMENT = 6,
/**
   Indicates a technote (formerly "event") artifact (kind of like a
   blog entry).
*/
FSL_SATYPE_TECHNOTE = 7,
/** Historical (deprecated) name for FSL_SATYPE_TECHNOTE. */
FSL_SATYPE_EVENT = 7,
/**
   Indicates a forum post artifact (a close relative of wiki pages).
*/
FSL_SATYPE_FORUMPOST = 8,
/**
   The number of CATYPE entries. Must be last in the enum. Used for loop
   control.
*/
FSL_SATYPE_count,

/**
   A pseudo-type for use with fsl_sym_to_rid() which changes the
   behavior of checkin lookups to return the RID of the start of the
   branch rather than the tip, with the caveat that the results are
   unspecified if the given symbolic name refers to multiple
   branches.

   fsl_satype_event_cstr() returns the same as FSL_SATYPE_CHECKIN for
   this entry.

   This entry IS NOT VALID for most APIs which require a fsl_satype_e
   value.
*/
FSL_SATYPE_BRANCH_START = 100 // MUST come after FSL_SATYPE_count
};
typedef enum fsl_satype_e fsl_satype_e;

/**
   Returns some arbitrary but distinct string for the given
   fsl_satype_e. The returned bytes are static and
   NUL-terminated. Intended primarily for debugging and informative
   purposes, not actual user output.
*/
FSL_EXPORT char const * fsl_satype_cstr(fsl_satype_e t);

/**
   For a given artifact type, it returns the key string used in the
   event.type db table. Returns NULL if passed an unknown value or
   a type which is not used in the event table, otherwise the
   returned bytes are static and NUL-terminated.

   The returned strings for a given type are as follows:

   - FSL_SATYPE_ANY returns "*"
   - FSL_SATYPE_CHECKIN and FSL_SATYPE_BRANCH_START return "ci"
   - FSL_SATYPE_WIKI returns "w"
   - FSL_SATYPE_TAG returns "g"
   - FSL_SATYPE_TICKET returns "t"
   - FSL_SATYPE_EVENT returns "e"

   The other control artifact types to not have representations
   in the event table, and NULL is returned for them.

   All of the returned values can be used in comparison clauses in
   queries on the event table's 'type' field (but use GLOB instead
   of '=' so that the "*" returned by FSL_ATYPE_ANY can match!).
   For example, to get the comments from the most recent 5 commits:

   @code
   SELECT
   datetime(mtime),
   coalesce(ecomment,comment),
   user
   FROM event WHERE type='ci'
   ORDER BY mtime DESC LIMIT 5; 
   @endcode

   Where 'ci' in the SQL is the non-NULL return value from this
   function. When escaping this value via fsl_buffer_appendf() (or
   anything functionally similar), use the %%q/%%Q format
   specifiers to escape it.
*/
FSL_EXPORT char const * fsl_satype_event_cstr(fsl_satype_e t);  
/**
   A collection of bitmaskable values indicating categories
   of fossil-standard glob sets. These correspond to the following
   configurable settings:

   ignore-glob, crnl-glob, binary-glob
*/
enum fsl_glob_category_t{
/** Corresponds to the ignore-glob config setting. */
FSL_GLOBS_IGNORE = 0x01,
/** Corresponds to the crnl-glob config setting. */
FSL_GLOBS_CRNL = 0x02,
/** Corresponds to the binary-glob config setting. */
FSL_GLOBS_BINARY = 0x04,
/** A superset of all config-level glob categories. */
FSL_GLOBS_ANY = 0xFF
};
typedef enum fsl_glob_category_t fsl_glob_category_t;

/**
   Checks one or more of f's configurable glob lists to see if str
   matches one of them. If it finds a match, it returns a pointer to
   the matching glob (as per fsl_glob_list_matches()), the bytes
   of which are owned by f and may be invalidated via modification
   or reloading of the underlying glob list. In generally the return
   value can be used as a boolean - clients generally do not need
   to know exactly which glob matched.

   gtype specifies the glob list(s) to check in the form of a
   bitmask of fsl_glob_category_t values. Note that the order of the
   lists is unspecified, so if that is important for you then be
   sure that gtype only specifies one glob list
   (e.g. FSL_GLOBS_IGNORE) and call it again (e.g. passing
   FSL_GLOBS_BINARY) if you need to distinguish between those two
   cases.

   str must be a non-NULL, non-empty empty string.

   Returns NULL if !f, !str, !*str, gtype does not specify any known
   glob list(s), or no glob match is found.

   Performance is, abstractly speaking, horrible, because we're
   comparing arbitrarily long lists of glob patterns against an
   arbitrary string. That said, it's fast enough for our purposes.
*/
FSL_EXPORT char const * fsl_cx_glob_matches( fsl_cx * f, int gtype,
                                             char const * str );

/**
   Sets f's hash policy and returns the previous value. If f has a
   repository db open then the setting is stored there and any error
   in setting it is placed into f's error state but otherwise ignored
   for purposes of this call.

   If p is FSL_HPOLICY_AUTO *and* the current repository contains any
   SHA3-format hashes, the policy is interpreted as FSL_HPOLICY_SHA3.

   This value is a *suggestion*, and may be trumped by various
   conditions, in particular in repositories containing older (SHA1)
   hashes.
*/
FSL_EXPORT fsl_hashpolicy_e fsl_cx_hash_policy_set(fsl_cx *f, fsl_hashpolicy_e p);

/**
   Returns f's current hash policy.
*/
FSL_EXPORT fsl_hashpolicy_e fsl_cx_hash_policy_get(fsl_cx const*f);

/**
   Returns a human-friendly name for the given policy, or NULL for an
   invalid policy value. The returned strings are the same ones used
   by fossil's hash-policy command.
*/
FSL_EXPORT char const * fsl_hash_policy_name(fsl_hashpolicy_e p);

/**
   Hashes all of pIn, appending the hash to pOut. Returns 0 on succes,
   FSL_RC_OOM if allocation of space in pOut fails. The hash algorithm
   used depends on the given fossil context's current hash policy and
   the value of the 2nd argument:

   If the 2nd argument is false, the hash is performed per the first
   argument's current hash policy. If the 2nd argument is true, the
   hash policy is effectively inverted. e.g. if the context prefers
   SHA3 hashes, the alternate form will use SHA1.

   Returns FSL_RC_UNSUPPORTED, without updating f's error state, if
   the hash is not possible due to conflicting values for the policy
   and its alternate. e.g. a context with policy FSL_HPOLICY_SHA3_ONLY
   will refuse to apply an SHA1 hash. Whether or not this result can
   be ignored is context-dependent, but it normally can be. This
   result is only possible when the 2nd argument is true.

   Returns 0 on success.
*/
FSL_EXPORT int fsl_cx_hash_buffer( const fsl_cx * f, bool useAlternate,
                                   fsl_buffer const * pIn,
                                   fsl_buffer * pOut);
/**
   The file counterpart of fsl_cx_hash_buffer(), behaving exactly the
   same except that its data source is a file and it may return
   various error codes from fsl_buffer_fill_from_filename(). Note that
   the contents of the file, not its name, are hashed.
*/
FSL_EXPORT int fsl_cx_hash_filename( fsl_cx * f, bool useAlternate,
                                     const char * zFilename, fsl_buffer * pOut);

/**
   Works like fsl_getcwd() but updates f's error state on error and
   appends the current directory's name to the given buffer. Returns 0
   on success.
*/
FSL_EXPORT int fsl_cx_getcwd(fsl_cx * f, fsl_buffer * pOut);

/**
   Returns the same as passing fsl_cx_db() to
   fsl_db_transaction_level(), or 0 if f has no db opened.

   @see fsl_cx_db()
*/
FSL_EXPORT int fsl_cx_transaction_level(fsl_cx * f);
/**
   Returns the same as passing fsl_cx_db() to
   fsl_db_transaction_begin().
*/
FSL_EXPORT int fsl_cx_transaction_begin(fsl_cx * f);
/**
   Returns the same as passing fsl_cx_db() to
   fsl_db_transaction_end().
*/
FSL_EXPORT int fsl_cx_transaction_end(fsl_cx * f, bool doRollback);

/**
   Installs or (if f is NULL) uninstalls a confirmation callback for
   use by operations on f which require user confirmation. The exact
   implications of *not* installing a confirmer depend on the
   operation in question: see fsl_cx_confirm().

   The 2nd argument bitwise copied into f's internal confirmer
   object. If the 2nd argument is NULL, f's confirmer is cleared,
   which will cause fsl_cx_confirm() to use certain default responses
   (see that function for details).

   If the final argument is not NULL then the previous confirmer is
   bitwise copied to it.

   @see fsl_confirm_callback_f
   @see fsl_cx_confirm()
   @see fsl_cx_confirmer_get()
*/
FSL_EXPORT void fsl_cx_confirmer(fsl_cx * f,
                                 fsl_confirmer const * newConfirmer,
                                 fsl_confirmer * prevConfirmer);
/**
   Stores a bitwise copy of f's current confirmer object into *dest. Can
   be used to save the confirmer before temporarily swapping it out.

   @see fsl_cx_confirmer()
*/
FSL_EXPORT void fsl_cx_confirmer_get(fsl_cx const * f, fsl_confirmer * dest);

/**
   If fsl_cx_confirmer() was used to install a confirmer callback in f
   then this routine calls that confirmer and returns its result code
   and its answer via *outAnswer. If no confirmer is currently
   installed, it responds with default answers, depending on the
   eventId:

   - FSL_CEVENT_OVERWRITE_MOD_FILE: FSL_CRESPONSE_NEVER

   - FSL_CEVENT_OVERWRITE_UNMGD_FILE: FSL_CRESPONSE_NEVER

   - FSL_CEVENT_RM_MOD_UNMGD_FILE: FSL_CRESPONSE_NEVER

   - FSL_CEVENT_MULTIPLE_VERSIONS: FSL_CRESPONSE_CANCEL

   Those are not 100% set in stone and are up for reconsideration.

   If a confirmer has been installed, this function does not modify
   outAnswer->response if the installed confirmer does not. Thus
   routines should set it to some acceptable default/sentinel value
   before calling this, to account for callbacks which ignore the
   given detail->eventId.

   If a confirmer callback responds with FSL_CRESPONSE_ALWAYS or
   FSL_CRESPONSE_NEVER, the code which is requesting confirmation must
   honor that by *NOT* calling the callback again for the current
   processing step of that eventId. e.g. if a loop asks for
   confirmation of FSL_CEVENT_RM_MOD_FILE and any response is one of
   the above, that one loop must not ask for confirmation again, and
   must instead accept that response for future queries within the
   same logical library operation (e.g. one checkout-update
   cycle). This is particularly important for applications which
   interactively present the question to the user for confirmation so
   that users have a way to *not* get spammed with a confirmation
   message showing up for each and every one of an arbitrary number of
   confirmations.

   @see fsl_confirm_callback_f
   @see fsl_cx_confirmer()
*/
FSL_EXPORT int fsl_cx_confirm(fsl_cx *f, fsl_confirm_detail const * detail,
                              fsl_confirm_response *outAnswer);

#if 0
/**
   DO NOT USE - not yet tested and ready.

   Returns the result of either localtime(clock) or gmtime(clock),
   depending on f:

   - If f is NULL, returns localtime(clock).

   - If f has had its FSL_CX_F_LOCALTIME_GMT flag set (see
   fsl_cx_flag_set()) then returns gmtime(clock), else
   localtime(clock).

   If clock is NULL, NULL is returned.

   Note that fsl_cx instances default to using UTC for everything,
   which is the opposite of fossil(1).
*/
FSL_EXPORT struct tm * fsl_cx_localtime( fsl_cx const * f, const time_t * clock );

/**
   Equivalent to fsl_cx_localtime(NULL, clock).
*/
FSL_EXPORT struct tm * fsl_localtime( const time_t * clock );

/**
   DO NOT USE - not yet tested and ready.

   This function passes (f, clock) to fsl_cx_localtime(),
   then returns the result of mktime(3) on it. So...
   it adjusts a UTC Unix timestamp to either the same UTC
   local timestamp or to the local time.
*/
FSL_EXPORT time_t fsl_cx_time_adj(fsl_cx const * f, time_t clock);
#endif

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






























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted include/fossil-scm/fossil-db.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
/* -*- 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_DB_H_INCLUDED)
#define ORG_FOSSIL_SCM_FSL_DB_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 public APIs for working with fossil's database
  abstraction layer.
*/

#include "fossil-core.h" /* MUST come first b/c of config macros */
/*
  We don't _really_ want to include sqlite3.h at this point, but if we
  do not then we have to typedef the sqlite3 struct here and that
  breaks when client code includes both this file and sqlite3.h.
*/
#include "sqlite3.h"

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

/**
   Potential TODO. Maybe not needed - v1 uses only(?) 1 hook and we
   can do that w/o hooks.
*/
typedef int (*fsl_commit_hook_f)( void * state );
/** Potential TODO */
struct fsl_commit_hook {
  fsl_commit_hook_f hook;
  int sequence;
  void * state;
};
#define fsl_commit_hook_empty_m {NULL,0,NULL}
typedef struct fsl_commit_hook fsl_commit_hook;
/* extern const fsl_commit_hook fsl_commit_hook_empty; */

/**
   Potential TODO.
*/
FSL_EXPORT int fsl_db_before_commit_hook( fsl_db * db, fsl_commit_hook_f f,
                               int sequence, void * state );


#if 0
/* We can't do this because it breaks when clients include both
   this header and sqlite3.h. Is there a solution which lets us
   _not_ include sqlite3.h from this file and also compiles when
   clients include both?
*/
#if !defined(SQLITE_OK)
/**
   Placeholder for sqlite3/4 type. We currently use v3 but will
   almost certainly switch to v4 at some point. Before we can do
   that we need an upgrade/migration path.
*/
typedef struct sqlite3 sqlite3;
#endif
#endif

/**
   A level of indirection to "hide" the actual db driver
   implementation from the public API. Whether or not the API
   uses/will use sqlite3 or 4 is "officially unspecified."  We
   currently use 3 because (A) it bootstraps development and
   testing by letting us use existing fossil repos for and (B) it
   reduces the number of potential problems when porting SQL-heavy
   code from the v1 tree. Clients should try not to rely on the
   underlying db driver API, but may need it for some uses
   (e.g. binding custom SQL functions).
*/
typedef sqlite3 fsl_dbh_t;

/**
   Db handle wrapper class. Each instance wraps a single sqlite
   database handle.

   Fossil is built upon sqlite3, but this abstraction is intended
   to hide that, insofar as possible, from clients so as to
   simplify an eventual port from v3 to v4. Clients should avoid
   relying on the underlying db being sqlite (or at least not rely
   on a specific version), but may want to register custom
   functions with the driver (or perform similar low-level
   operations) and the option is left open for them to access that
   handle via the fsl_db::dbh member.

   @see fsl_db_open();
   @see fsl_db_close();
   @see fsl_stmt
*/
struct fsl_db {
  /**
     Fossil Context on whose behalf this instance is operating, if
     any. Certain db operations behave differently depending
     on whether or not this is NULL.
  */
  fsl_cx * f;

  /**
     Describes what role(s) this db connection plays in fossil (if
     any). This is a bitmask of fsl_dbrole_e values, and a db
     connection may have multiple roles. This is only used by the
     fsl_cx-internal API.
  */
  int role;

  /**
     Underlying db driver handle.
  */
  fsl_dbh_t * dbh;

  /**
     Holds error state from the underlying driver.  fsl_db and
     fsl_stmt operations which fail at the driver level "should"
     update this state to include error info from the driver.
     fsl_cx APIs which fail at the DB level uplift this (using
     fsl_error_move()) so that they can pass it on up the call chain.
  */
  fsl_error error;

  /**
     Holds the file name used when opening this db. Might not refer to
     a real file (e.g. might be ":memory:" or "" (similar to
     ":memory:" but may swap to temp storage).

     Design note: we currently hold the name as it is passed to the
     db-open routine, without canonicalizing it. That is very possibly
     a mistake, as it makes it impossible to properly compare the name
     to another arbitrary checkout-relative name for purposes of
     fsl_reserved_fn_check(). For purposes of fsl_cx we currently
     (2021-03-12) canonicalize db's which we fsl_db_open(), but not
     those which we ATTACH (which includes the repo and checkout
     dbs). We cannot reasonably canonicalize the repo db filename
     because it gets written into the checkout db so that the checkout
     knows where to find the repository. History has shown that that
     path needs to be stored exactly as a user entered it, which is
     often relative.
  */
  char * filename;

  /**
     Holds the database name for use in creating queries.
     Might or might not be set/needed, depending on
     the context.
  */
  char * name;

  /**
     Debugging/test counter. Closing a db with opened statements
     might assert() or trigger debug output when the db is closed.
  */
  int openStatementCount;

  /**
     Counter for fsl_db_transaction_begin/end().
  */
  int beginCount;

  /**
     Internal flag for communicating rollback state through the
     call stack. If this is set to a true value,
     fsl_db_transaction_end() calls will behave like a rollback
     regardless of the value of the 2nd argument passed to that
     function. i.e. it propagates a rollback through nested
     transactions.

     Potential TODO: instead of treating this like a boolean, store
     the error number which caused the rollback here.  We'd have to
     go fix a lot of code for that, though :/.
  */
  int doRollback;

  /**
     Internal change counter. Set when a transaction is
     started/committed.

     Maintenance note: it's an int because that's what
     sqlite3_total_changes() returns.
  */
  int priorChanges;

  /**
     List of SQL commands (char *) which should be executed prior
     to a commit. This list is cleared when the transaction counter
     drops to zero as the result of fsl_db_transaction_end()
     or fsl_db_rollback_force().

     TODO? Use (fsl_stmt*) objects instead of strings? Depends on
     how much data we need to bind here (want to avoid an extra
     copy if we need to bind big stuff). That was implemented in
     [9d9375ac2d], but that approach prohibits multi-statement
     pre-commit triggers, so it was not trunked. It's still unknown
     whether we need multi-statement SQL in this context
     (==fossil's infrastructure).

     @see fsl_db_before_commit()
  */
  fsl_list beforeCommit;

  /**
     An internal cache of "static" queries - those which do not
     rely on call-time state unless that state can be
     bind()ed. Holds a linked list of (fsl_stmt*) instances.

     @see fsl_db_prepare_cached()
  */
  fsl_stmt * cacheHead;

  /**
     A marker which tells fsl_db_close() whether or not
     fsl_db_malloc() allocated this instance (in which case
     fsl_db_close() will fsl_free() it) or not (in which case it
     does not free() it).
  */
  void const * allocStamp;
};
/**
   Empty-initialized fsl_db structure, intended for const-copy
   initialization.
*/
#define fsl_db_empty_m {                        \
    NULL/*f*/,                                  \
      FSL_DBROLE_NONE,                         \
      NULL/*dbh*/,                              \
      fsl_error_empty_m /*error*/,              \
      NULL/*filename*/,                         \
      NULL/*name*/,                             \
      0/*openStatementCount*/,                  \
      0/*beginCount*/,                          \
      0/*doRollback*/,                          \
      0/*priorChanges*/,                        \
      fsl_list_empty_m/*beforeCommit*/,         \
      NULL/*cacheHead*/,                        \
      NULL/*allocStamp*/                        \
      }

/**
   Empty-initialized fsl_db structure, intended for copy
   initialization.
*/
FSL_EXPORT const fsl_db fsl_db_empty;

/**
   If db is not NULL then this function returns its name (the one
   used to open it). The bytes are valid until the db connection is
   closed or until someone mucks with db->filename. If len is not
   NULL then *len is (on success) assigned to the length of the
   returned string, in bytes.  The string is NUL-terminated, so
   fetching the length (by passing a non-NULL 2nd parameter) is
   optional but sometimes helps improve efficiency be removing
   the need for a downstream call to fsl_strlen().

   Returns NULL if !f or f has no checkout opened.
*/
FSL_EXPORT char const * fsl_db_filename(fsl_db const * db, fsl_size_t * len);

typedef sqlite3_stmt fsl_stmt_t;
/**
   Represents a prepared statement handle.
   Intended usage:

   @code
   fsl_stmt st = fsl_stmt_empty;
   int rc = fsl_db_prepare( db, &st, "..." );
   if(rc){ // Error!
   assert(!st.stmt);
   // db->error might hold driver-level error details.
   }else{
   // use st and eventually finalize it:
   fsl_stmt_finalize( &st );
   }
   @endcode


   Script binding implementations can largely avoid exposing the
   statement handle (and its related cleanup ordering requirements)
   to script code. They need to have some mechanism for binding
   values to SQL (or implement all the escaping themselves), but
   that can be done without exposing all of the statement class if
   desired. For example, here's some hypothetical script code:

   @code
   var st = db.prepare(".... where i=:i and x=:x");
   // st is-a Statement, but we need not add script bindings for
   // the whole Statement.bind() API. We can instead simplify that
   // to something like:
   try {
   st.exec( {i: 42, x: 3} )
   // or, for a SELECT query:
   st.each({
   bind{i:42, x:3},
   rowType: 'array', // or 'object'
   callback: function(row,state,colNames){ print(row.join('\t')); },
   state: {...callback function state...}
   });
   } finally {
   st.finalize();
   // It is critical that st gets finalized before its DB, and
   // that'shard to guaranty if we leave st to the garbage collector!
   }
   // see below for another (less messy) alternative
   @endcode

   Ideally, script code should not have direct access to the
   Statement because managing lifetimes can be difficult in the
   face of flow-control changes caused by exceptions (as the above
   example demonstrates). Statements can be completely hidden from
   clients if the DB wrapper is written to support it. For example,
   in pseudo-JavaScript that might look like:

   @code
   db.exec("...where i=? AND x=?", 42, 3);
   db.each({sql:"select ... where id<?", bind:[10],
   rowType: 'array', // or 'object'
   callback: function(row,state,colNames){ print(row.join('\t')); },
   state: {...arbitrary state for the callback...}
   });
   @endcode
*/
struct fsl_stmt {
  /**
     The db which prepared this statement.
  */
  fsl_db * db;

  /**
     Underlying db driver-level statement handle. Clients should
     not rely on the specify concrete type if they can avoid it, to
     simplify an eventual port from sqlite3 to sqlite4.
  */
  fsl_stmt_t * stmt;

  /**
     SQL used to prepare this statement.
  */
  fsl_buffer sql;

  /**
     Number of result columns in this statement. Cached when the
     statement is prepared. Is a signed type because the underlying
     API does it this way.
  */
  int colCount;

  /**
     Number of bound parameter indexes in this statement. Cached
     when the statement is prepared. Is a signed type because the
     underlying API does it this way.
  */
  int paramCount;

  /**
     The number of times this statement has fetched a row via
     fsl_stmt_step().
  */
  fsl_size_t rowCount;

  /**
     Internal state flags.
  */
  int flags;

  /**
     For _internal_ use in creating linked lists. Clients _must_not_
     modify this field.
   */
  fsl_stmt * next;

  /**
     A marker which tells fsl_stmt_finalize() whether or not
     fsl_stmt_malloc() allocated this instance (in which case
     fsl_stmt_finalize() will fsl_free() it) or not (in which case
     it does not free() it).
  */
  void const * allocStamp;
};
/**
   Empty-initialized fsl_stmt instance, intended for use as an
   in-struct initializer.
*/
#define fsl_stmt_empty_m {                      \
    NULL/*db*/,                                 \
      NULL/*stmt*/,                             \
      fsl_buffer_empty_m/*sql*/,                \
      0/*colCount*/,                            \
      0/*paramCount*/,                          \
      0/*rowCount*/,                            \
      1/*flags*/,                               \
      NULL/*next*/,                             \
      NULL/*allocStamp*/                        \
      }

/**
   Empty-initialized fsl_stmt instance, intended for
   copy-constructing.
*/
FSL_EXPORT const fsl_stmt fsl_stmt_empty;

/**
   Allocates a new, cleanly-initialized fsl_stmt instance using
   fsl_malloc(). The returned pointer must eventually be passed to
   fsl_stmt_finalize() to free it (whether or not it is ever passed
   to fsl_db_prepare()).

   Returns NULL on allocation error.
*/
FSL_EXPORT fsl_stmt * fsl_stmt_malloc();


/**
   If db is not NULL this behaves like fsl_error_get(), using the
   db's underlying error state. If !db then it returns
   FSL_RC_MISUSE.
*/
FSL_EXPORT int fsl_db_err_get( fsl_db const * db, char const ** msg, fsl_size_t * len );

/**
   Resets any error state in db, but might keep the string
   memory allocated for later use.
*/
FSL_EXPORT void fsl_db_err_reset( fsl_db * db );

/**
   Prepares an SQL statement for execution. On success it returns
   0, populates tgt with the statement's state, and the caller is
   obligated to eventually pass tgt to fsl_stmt_finalize(). tgt
   must have been cleanly initialized, either via allocation via
   fsl_stmt_malloc() or by copy-constructing fsl_stmt_empty
   resp. fsl_stmt_empty_m (depending on the context).

   On error non-0 is returned and tgt is not modified. If
   preparation of the statement fails at the db level then
   FSL_RC_DB is returned f's error state (fsl_cx_err_get())
   "should" contain more details about the problem. Returns
   FSL_RC_MISUSE if !db, !callback, or !sql. Returns
   FSL_RC_NOT_FOUND if db is not opened. Returns FSL_RC_RANGE if
   !*sql.

   The sql string and the following arguments get routed through
   fsl_appendf(), so any formatting options supported by that
   routine may be used here. In particular, the %%q and %%Q
   formatting options are intended for use in escaping SQL for
   routines such as this one.

   Compatibility note: in sqlite, empty SQL code evaluates
   successfully but with a NULL statement. This API disallows empty
   SQL because it uses NULL as a "no statement" marker and because
   empty SQL is arguably not a query at all.

   Tips:

   - fsl_stmt_col_count() can be used to determine whether a
   statement is a fetching query (fsl_stmt_col_count()>0) or not
   (fsl_stmt_col_count()==0) without having to know the contents
   of the query.

   - fsl_db_prepare_cached() can be used to cache often-used or
   expensive-to-prepare queries within the context of their parent
   db handle.
*/
FSL_EXPORT int fsl_db_prepare( fsl_db *db, fsl_stmt * tgt, char const * sql, ... );

/**
   va_list counterpart of fsl_db_prepare().
*/
FSL_EXPORT int fsl_db_preparev( fsl_db *db, fsl_stmt * tgt, char const * sql, va_list args );

/**
   A special-purpose variant of fsl_db_prepare() which caches
   statements based on their SQL code. This works very much like
   fsl_db_prepare() and friends except that it can return the same
   statement (via *st) multiple times (statements with identical
   SQL are considered equivalent for caching purposes). Clients
   need not explicitly pass the returned statement to
   fsl_stmt_finalize() - the db holds these statements and will
   finalize them when it is closed. It is legal to pass them to
   finalize, in which case they will be cleaned up immediately but
   that also invalidates _all_ pointers to the shared instances.

   If client code does not call fsl_stmt_finalize(), it MUST pass
   the statement pointer to fsl_stmt_cached_yield(st) after is done
   with it. That makes the query available for use again with this
   routine. If a cached query is not yielded via
   fsl_stmt_cached_yield() then this routine will return
   FSL_RC_ACCESS on subsequent requests for that SQL to prevent
   that recursive (mis)use of the statement causes problems.

   This routine is intended to be used in oft-called routines
   where the cost of re-creating statements on each execution could
   be prohibitive (or at least a bummer).

   Returns 0 on success, FSL_RC_MISUSE if any arguments are
   invalid. On error, *st is not written to.  On other error's
   db->error might be updated with more useful information.  See the
   Caveats section below for more details.

   Its intended usage looks like:

   @code
   fsl_stmt * st = NULL;
   int rc = fsl_db_prepare_cached(myDb, &st, "SELECT ...");
   if(rc) { assert(!st); ...error... }
   else {
   ...use it, and _be sure_ to yield it when done:...
   fsl_stmt_cached_yield(st);
   }
   @endcode

   Though this function allows a formatted SQL string, caching is
   generally only useful with statements which have "static" SQL,
   i.e. no call-dependent values embedded within the SQL. It _can_,
   however, contain bind() placeholders which get reset for each
   use. Note that fsl_stmt_cached_yield() resets the statement, so
   most uses of cached statements do not require that the client
   explicitly reset cached statements (doing so is harmless,
   however).

   Caveats:

   Cached queries must not be used in contexts where recursion
   might cause the same query to be returned from this function
   while it is being processed at another level in the execution
   stack. Results would be undefined. Caching is primarily intended
   for often-used routines which bind and fetch simple values, and
   not for queries which bind large inlined values or might invoke
   recursion. Because of the potential for recursive breakage, this
   function flags queries it doles out and requires that clients
   call fsl_stmt_cached_yield() to un-flag them for re-use. It will
   return FSL_RC_ACCESS if an attempt is made to (re)prepare a
   statement for which a fsl_stmt_cached_yield() is pending, and
   db->error will be populated with a (long) error string
   descripting the problem and listing the SQL which caused the
   collision/misuse.


   Design note: for the recursion/parallel use case we "could"
   reimplement this to dole out a new statement (e.g. by appending
   " -- a_number" to the SQL to bypass the collision) and free it in
   fsl_stmt_cached_yield(), but that (A) gets uglier than it needs
   to be and (B) is not needed unless/until we really need cached
   queries in spots which would normally break them. The whole
   recursion problem is still theoretical at this point but could
   easily affect small, often-used queries without recursion.

   @see fsl_db_stmt_cache_clear()
   @see fsl_stmt_cached_yield()
*/
FSL_EXPORT int fsl_db_prepare_cached( fsl_db * db, fsl_stmt ** st, char const * sql, ... );

/**
   The va_list counterpart of fsl_db_prepare_cached().
*/
FSL_EXPORT int fsl_db_preparev_cached( fsl_db * db, fsl_stmt ** st, char const * sql,
                            va_list args );

/**
   "Yields" a statement which was prepared with
   fsl_db_prepare_cached(), such that that routine can once again
   use/re-issue that statement. Statements prepared this way must
   be yielded in order to prevent that recursion causes
   difficult-to-track errors when a given cached statement is used
   concurrently in different code contexts.

   If st is not NULL then this also calls fsl_stmt_reset() on the
   statement (because that simplifies usage of cached statements).

   Returns 0 on success, FSL_RC_MISUSE if !st or if st does not
   appear to have been doled out from fsl_db_prepare_cached().

   @see fsl_db_prepare_cached()
   @see fsl_db_stmt_cache_clear()
*/
FSL_EXPORT int fsl_stmt_cached_yield( fsl_stmt * st );

/**
   Immediately cleans up all cached statements.  Returns the number
   of statements cleaned up. It is illegal to call this while any
   of the cached statements are actively being used (have not been
   fsl_stmt_cached_yield()ed), and doing so will lead to undefined
   results if the statement(s) in question are used after this
   function completes.

   @see fsl_db_prepare_cached()
   @see fsl_stmt_cached_yield()
*/
FSL_EXPORT fsl_size_t fsl_db_stmt_cache_clear(fsl_db * db);

/**
   A special-purposes utility which schedules SQL to be executed
   the next time fsl_db_transaction_end() commits a transaction for
   the given db. A commit or rollback will clear all before-commit
   SQL whether it executes them or not. This should not be used as
   a general-purpose trick, and is intended only for use in very
   limited parts of the Fossil infrastructure.

   Before-commit code is only executed if the db has made changes
   since the transaction began. If no changes are recorded
   then before-commit triggers are _not_ run. This is a historical
   behaviour which is up for debate.

   This function does not prepare the SQL, so it does not catch
   errors which happen at prepare-time. Preparation is done (if
   ever) just before the next transaction is committed.

   Returns 0 on success, non-0 on error.

   Potential TODO: instead of storing the raw SQL, prepare the
   statements here and store the statement handles. The main
   benefit would be that this routine could report preport
   preparation errors (which otherwise cause the the commit to
   fail). The down-side is that it prohibits the use of
   multi-statement pre-commit code. We have an implementation of
   this somewhere early on in the libfossil tree, but it was not
   integrated because of the inability to use multi-statement SQL
   with it.
*/
FSL_EXPORT int fsl_db_before_commit( fsl_db *db, char const * sql, ... );

/**
   va_list counterpart to fsl_db_before_commit().
*/
FSL_EXPORT int fsl_db_before_commitv( fsl_db *db, char const * sql, va_list args );


/**
   Frees memory associated with stmt but does not free stmt unless
   it was allocated by fsl_stmt_malloc() (these objects are
   normally stack-allocated, and such object must be initialized by
   copying fsl_stmt_empty so that this function knows whether or
   not to fsl_free() them). Returns FSL_RC_MISUSE if !stmt or it
   has already been finalized (but was not freed).
*/
FSL_EXPORT int fsl_stmt_finalize( fsl_stmt * stmt );

/**
   "Steps" the given SQL cursor one time and returns one of the
   following: FSL_RC_STEP_ROW, FSL_RC_STEP_DONE, FSL_RC_STEP_ERROR.
   On a db error this will update the underlying db's error state.
   This function increments stmt->rowCount by 1 if it returns
   FSL_RC_STEP_ROW.

   Returns FSL_RC_MISUSE if !stmt or stmt has not been prepared.

   It is only legal to call the fsl_stmt_g_xxx() and
   fsl_stmt_get_xxx() functions if this functon returns
   FSL_RC_STEP_ROW. FSL_RC_STEP_DONE is returned upon successfully
   ending iteration or if there is no iteration to perform (e.g. a
   UPDATE or INSERT).


   @see fsl_stmt_reset()
   @see fsl_stmt_reset2()
   @see fsl_stmt_each()
*/
FSL_EXPORT int fsl_stmt_step( fsl_stmt * stmt );

/**
   A callback interface for use with fsl_stmt_each() and
   fsl_db_each(). It will be called one time for each row fetched,
   passed the statement object and the state parameter passed to
   fsl_stmt_each() resp. fsl_db_each().  If it returns non-0 then
   iteration stops and that code is returned UNLESS it returns
   FSL_RC_BREAK, in which case fsl_stmt_each() stops iteration and
   returns 0. i.e. implementations may return FSL_RC_BREAK to
   prematurly end iteration without causing an error.

   This callback is not called for non-fetching queries or queries
   which return no results, though it might (or might not) be
   interesting for it to do so, passing a NULL stmt for that case.

   stmt->rowCount can be used to determine how many times the
   statement has called this function. Its counting starts at 1.

   It is strictly illegal for a callback to pass stmt to
   fsl_stmt_step(), fsl_stmt_reset(), fsl_stmt_finalize(), or any
   similar routine which modifies its state. It must only read the
   current column data (or similar metatdata, e.g. column names)
   from the statement, e.g. using fsl_stmt_g_int32(),
   fsl_stmt_get_text(), or similar.
*/
typedef int (*fsl_stmt_each_f)( fsl_stmt * stmt, void * state );

/**
   Calls the given callback one time for each result row in the
   given statement, iterating over stmt using fsl_stmt_step(). It
   applies no meaning to the callbackState parameter, which gets
   passed as-is to the callback. See fsl_stmt_each_f() for the
   semantics of the callback.

   Returns 0 on success. Returns FSL_RC_MISUSE if !stmt or
   !callback.
*/
FSL_EXPORT int fsl_stmt_each( fsl_stmt * stmt, fsl_stmt_each_f callback,
                              void * callbackState );

/**
   Resets the given statement, analog to sqlite3_reset(). Should be
   called one time between fsl_stmt_step() iterations when running
   multiple INSERTS, UPDATES, etc. via the same statement. If
   resetRowCounter is true then the statement's row counter
   (st->rowCount) is also reset to 0, else it is left
   unmodified. (Most use cases don't use the row counter.)

   Returns 0 on success, FSL_RC_MISUSE if !stmt or stmt has not
   been prepared, FSL_RC_DB if the underlying reset fails (in which
   case the error state of the stmt->db handle is updated to
   contain the error information).

   @see fsl_stmt_db()
   @see fsl_stmt_reset()
*/
FSL_EXPORT int fsl_stmt_reset2( fsl_stmt * stmt, bool resetRowCounter );

/**
   Equivalent to fsl_stmt_reset2(stmt, 0).
*/
FSL_EXPORT int fsl_stmt_reset( fsl_stmt * stmt );

/**
   Returns the db handle which prepared the given statement, or
   NULL if !stmt or stmt has not been prepared.
*/
FSL_EXPORT fsl_db * fsl_stmt_db( fsl_stmt * stmt );

/**
   Returns the SQL string used to prepare the given statement, or
   NULL if !stmt or stmt has not been prepared. If len is not NULL
   then *len is set to the length of the returned string (which is
   NUL-terminated). The returned bytes are owned by stmt and are
   invalidated when it is finalized.
*/
FSL_EXPORT char const * fsl_stmt_sql( fsl_stmt * stmt, fsl_size_t * len );

/**
   Returns the name of the given 0-based result column index, or
   NULL if !stmt, stmt is not prepared, or index is out out of
   range. The returned bytes are owned by the statement object and
   may be invalidated shortly after this is called, so the caller
   must copy the returned value if it needs to have any useful
   lifetime guarantees. It's a bit more complicated than this, but
   assume that any API calls involving the statement handle might
   invalidate the column name bytes.

   The API guarantees that the returned value is either NULL or
   NUL-terminated.

   @see fsl_stmt_param_count()
   @see fsl_stmt_col_count()
*/
FSL_EXPORT char const * fsl_stmt_col_name(fsl_stmt * stmt, int index);

/**
   Returns the result column count for the given statement, or -1 if
   !stmt or it has not been prepared. Note that this value is cached
   when the statement is created. Note that non-fetching queries
   (e.g. INSERT and UPDATE) have a column count of 0. Some non-SELECT
   constructs, e.g. PRAGMA table_info(tname), behave like SELECT
   and have a positive column count.

   @see fsl_stmt_param_count()
   @see fsl_stmt_col_name()
*/
FSL_EXPORT int fsl_stmt_col_count( fsl_stmt const * stmt );

/**
   Returns the bound parameter count for the given statement, or -1
   if !stmt or it has not been prepared. Note that this value is
   cached when the statement is created.

   @see fsl_stmt_col_count()
   @see fsl_stmt_col_name()
*/
FSL_EXPORT int fsl_stmt_param_count( fsl_stmt const * stmt );

/**
   Returns the index of the given named parameter for the given
   statement, or -1 if !stmt or stmt is not prepared.
*/
FSL_EXPORT int fsl_stmt_param_index( fsl_stmt * stmt, char const * param);

/**
   Binds NULL to the given 1-based parameter index.  Returns 0 on
   succcess. Sets the DB's error state on error.
*/
FSL_EXPORT int fsl_stmt_bind_null( fsl_stmt * stmt, int index );

/**
   Equivalent to fsl_stmt_bind_null_name() but binds to
   a named parameter.
*/
FSL_EXPORT int fsl_stmt_bind_null_name( fsl_stmt * stmt, char const * param );

/**
   Binds v to the given 1-based parameter index.  Returns 0 on
   succcess. Sets the DB's error state on error.
*/
FSL_EXPORT int fsl_stmt_bind_int32( fsl_stmt * stmt, int index, int32_t v );

/**
   Equivalent to fsl_stmt_bind_int32() but binds to a named
   parameter.
*/
FSL_EXPORT int fsl_stmt_bind_int32_name( fsl_stmt * stmt, char const * param, int32_t v );

/**
   Binds v to the given 1-based parameter index.  Returns 0 on
   succcess. Sets the DB's error state on error.
*/
FSL_EXPORT int fsl_stmt_bind_int64( fsl_stmt * stmt, int index, int64_t v );

/**
   Equivalent to fsl_stmt_bind_int64() but binds to a named
   parameter.
*/
FSL_EXPORT int fsl_stmt_bind_int64_name( fsl_stmt * stmt, char const * param, int64_t v );

/**
   Binds v to the given 1-based parameter index.  Returns 0 on
   succcess. Sets the Fossil context's error state on error.
*/
FSL_EXPORT int fsl_stmt_bind_double( fsl_stmt * stmt, int index, double v );

/**
   Equivalent to fsl_stmt_bind_double() but binds to a named
   parameter.
*/
FSL_EXPORT int fsl_stmt_bind_double_name( fsl_stmt * stmt, char const * param, double v );

/**
   Binds v to the given 1-based parameter index.  Returns 0 on
   succcess. Sets the DB's error state on error.
*/
FSL_EXPORT int fsl_stmt_bind_id( fsl_stmt * stmt, int index, fsl_id_t v );

/**
   Equivalent to fsl_stmt_bind_id() but binds to a named
   parameter.
*/
FSL_EXPORT int fsl_stmt_bind_id_name( fsl_stmt * stmt, char const * param, fsl_id_t v );

/**
   Binds the first n bytes of v as text to the given 1-based bound
   parameter column in the given statement. If makeCopy is true then
   the binding makes an copy of the data. Set makeCopy to false ONLY
   if you KNOW that the bytes will outlive the binding.

   Returns 0 on success. On error stmt's underlying db's error state
   is updated, hopefully with a useful error message.
*/
FSL_EXPORT int fsl_stmt_bind_text( fsl_stmt * stmt, int index,
                                   char const * v, fsl_int_t n,
                                   bool makeCopy );

/**
   Equivalent to fsl_stmt_bind_text() but binds to a named
   parameter.
*/
FSL_EXPORT int fsl_stmt_bind_text_name( fsl_stmt * stmt, char const * param,
                                        char const * v, fsl_int_t n,
                                        bool makeCopy );
/**
   Binds the first n bytes of v as a blob to the given 1-based bound
   parameter column in the given statement. See fsl_stmt_bind_text()
   for the semantics of the makeCopy parameter and return value.
*/
FSL_EXPORT int fsl_stmt_bind_blob( fsl_stmt * stmt, int index,
                                   void const * v, fsl_size_t len,
                                   bool makeCopy );

/**
   Equivalent to fsl_stmt_bind_blob() but binds to a named
   parameter.
*/
FSL_EXPORT int fsl_stmt_bind_blob_name( fsl_stmt * stmt, char const * param,
                                        void const * v, fsl_int_t len,
                                        bool makeCopy );

/**
   Gets an integer value from the given 0-based result set column,
   assigns *v to that value, and returns 0 on success.

   Returns FSL_RC_RANGE if index is out of range for stmt.
*/
FSL_EXPORT int fsl_stmt_get_int32( fsl_stmt * stmt, int index, int32_t * v );

/**
   Gets an integer value from the given 0-based result set column,
   assigns *v to that value, and returns 0 on success.

   Returns FSL_RC_RANGE if index is out of range for stmt.
*/
FSL_EXPORT int fsl_stmt_get_int64( fsl_stmt * stmt, int index, int64_t * v );

/**
   The fsl_id_t counterpart of fsl_stmt_get_int32(). Depending on
   the sizeof(fsl_id_t), it behaves as one of fsl_stmt_get_int32()
   or fsl_stmt_get_int64().
*/
FSL_EXPORT int fsl_stmt_get_id( fsl_stmt * stmt, int index, fsl_id_t * v );

/**
   Convenience form of fsl_stmt_get_id() which returns the value
   directly but cannot report errors. It returns -1 on error, but
   that is not unambiguously an error value.
*/
FSL_EXPORT fsl_id_t fsl_stmt_g_id( fsl_stmt * stmt, int index );

/**
   Convenience form of fsl_stmt_get_int32() which returns the value
   directly but cannot report errors. It returns 0 on error, but
   that is not unambiguously an error.
*/
FSL_EXPORT int32_t fsl_stmt_g_int32( fsl_stmt * stmt, int index );

/**
   Convenience form of fsl_stmt_get_int64() which returns the value
   directly but cannot report errors. It returns 0 on error, but
   that is not unambiguously an error.
*/
FSL_EXPORT int64_t fsl_stmt_g_int64( fsl_stmt * stmt, int index );

/**
   Convenience form of fsl_stmt_get_double() which returns the value
   directly but cannot report errors. It returns 0 on error, but
   that is not unambiguously an error.
*/
FSL_EXPORT double fsl_stmt_g_double( fsl_stmt * stmt, int index );

/**
   Convenience form of fsl_stmt_get_text() which returns the value
   directly but cannot report errors. It returns NULL on error, but
   that is not unambiguously an error because it also returns NULL
   if the column contains an SQL NULL value. If outLen is not NULL
   then it is set to the byte length of the returned string.
*/
FSL_EXPORT char const * fsl_stmt_g_text( fsl_stmt * stmt, int index, fsl_size_t * outLen );

/**
   Gets double value from the given 0-based result set column,
   assigns *v to that value, and returns 0 on success.

   Returns FSL_RC_RANGE if index is out of range for stmt.
*/
FSL_EXPORT int fsl_stmt_get_double( fsl_stmt * stmt, int index, double * v );

/**
   Gets a string value from the given 0-based result set column,
   assigns *out (if out is not NULL) to that value, assigns *outLen
   (if outLen is not NULL) to *out's length in bytes, and returns 0
   on success. Ownership of the string memory is unchanged - it is owned
   by the statement and the caller should immediately copy it if
   it will be needed for much longer.

   Returns FSL_RC_RANGE if index is out of range for stmt.
*/
FSL_EXPORT int fsl_stmt_get_text( fsl_stmt * stmt, int index, char const **out,
                       fsl_size_t * outLen );

/**
   The Blob counterpart of fsl_stmt_get_text(). Identical to that
   function except that its output result (3rd paramter) type
   differs, and it fetches the data as a raw blob, without any sort
   of string interpretation.

   Returns FSL_RC_RANGE if index is out of range for stmt.
*/
FSL_EXPORT int fsl_stmt_get_blob( fsl_stmt * stmt, int index, void const **out, fsl_size_t * outLen );

/**
   Executes multiple SQL statements, ignoring any results they might
   collect. Returns 0 on success, non-0 on error.  On error
   db->error might be updated to report the problem.
*/
FSL_EXPORT int fsl_db_exec_multi( fsl_db * db, const char * sql, ...);

/**
   va_list counterpart of db_exec_multi().
*/
FSL_EXPORT int fsl_db_exec_multiv( fsl_db * db, const char * sql, va_list args);

/**
   Executes a single SQL statement, skipping over any results
   it may have. Returns 0 on success. On error db's error state
   may be updated.
*/
FSL_EXPORT int fsl_db_exec( fsl_db * db, char const * sql, ... );

/**
   va_list counterpart of fs_db_exec().
*/
FSL_EXPORT int fsl_db_execv( fsl_db * db, char const * sql, va_list args );

/**
   Begins a transaction on the given db. Nested transactions are
   not directly supported but the db handle keeps track of
   open/close counts, such that fsl_db_transaction_end() will not
   actually do anything until the transaction begin/end counter
   goes to 0. Returns FSL_RC_MISUSE if !db or the db is not
   connected, else the result of the underlying db call(s).

   Transactions are an easy way to implement "dry-run" mode for
   some types of applications. For example:

   @code
   char dryRunMode = ...;
   fsl_db_transaction_begin(db);
   ...do your stuff...
   fsl_db_transaction_end(db, dryRunMode ? 1 : 0);
   @endcode

   Here's a tip for propagating error codes when using
   transactions:

   @code
   ...
   if(rc) fsl_db_transaction_end(db, 1);
   else rc = fsl_db_transaction_end(db, 0);
   @endcode

   That ensures that we propagate rc in the face of a rollback but
   we also capture the rc for a commit (which might yet fail). Note
   that a rollback in and of itself is not an error (though it also
   might fail, that would be "highly unusual" and indicative of
   other problems), and we certainly don't want to overwrite that
   precious non-0 rc with a successful return result from a
   rollback (which would, in effect, hide the error from the
   client).
*/
FSL_EXPORT int fsl_db_transaction_begin(fsl_db * db);

/**
   Equivalent to fsl_db_transaction_end(db, 0).
*/
FSL_EXPORT int fsl_db_transaction_commit(fsl_db * db);

/**
   Equivalent to fsl_db_transaction_end(db, 1).
*/
FSL_EXPORT int fsl_db_transaction_rollback(fsl_db * db);

/**
   Forces a rollback of any pending transaction in db, regardless
   of the internal transaction begin/end counter. Returns
   FSL_RC_MISUSE if !db or db is not opened, else returns the value
   of the underlying ROLLBACK call. This also re-sets/frees any
   transaction-related state held by db (e.g. db->beforeCommit).
   Use with care, as this mucks about with db state in a way which
   is not all that pretty and it may confuse downstream code.

   Returns 0 on success.
*/
FSL_EXPORT int fsl_db_rollback_force(fsl_db * db);

/**
   Decrements the transaction counter incremented by
   fsl_db_transaction_begin() and commits or rolls back the
   transaction if the counter goes to 0.

   If doRollback is true then this rolls back (or schedules a
   rollback of) a transaction started by
   fsl_db_transaction_begin(). If doRollback is false is commits
   (or schedules a commit).

   If db fsl_db_transaction_begin() is used in a nested manner and
   doRollback is true for any one of the nested calls, then that
   value will be remembered, such that the downstream calls to this
   function within the same transaction will behave like a rollback
   even if they pass 0 for the second argument.

   Returns FSL_RC_MISUSE if !db or the db is not opened, 0 if
   the transaction counter is above 0, else the result of the
   (potentially many) underlying database operations.

   Unfortunate low-level co-dependency: if db->f is not NULL and
   (db->role & FSL_DBROLE_REPO) then this function may perform
   extra repository-related post-processing on any commit, and
   checking the result code is particularly important for those
   cases.
*/
FSL_EXPORT int fsl_db_transaction_end(fsl_db * db, bool doRollback);

/**
   Returns the given db's current transaction depth. If the value is
   negative, its absolute value represents the depth but indicates
   that a rollback is pending. If it is positive, the transaction is
   still in a "good" state. If it is 0, no transaction is active.
*/
FSL_EXPORT int fsl_db_transaction_level(fsl_db * db);

/**
   Runs the given SQL query on the given db and returns true if the
   query returns any rows, else false. Returns 0 for any error as
   well.
*/
FSL_EXPORT bool fsl_db_exists(fsl_db * db, char const * sql, ... );

/**
   va_list counterpart of fsl_db_exists().
*/
FSL_EXPORT bool fsl_db_existsv(fsl_db * db, char const * sql, va_list args );

/**
   Runs a fetch-style SQL query against DB and returns the first
   column of the first result row via *rv. If the query returns no
   rows, *rv is not modified. The intention is that the caller sets
   *rv to his preferred default (or sentinel) value before calling
   this.

   The format string (the sql parameter) accepts all formatting
   options supported by fsl_appendf().

   Returns 0 on success. On error db's error state is updated and
   *rv is not modified.

   Returns FSL_RC_MISUSE without side effects if !db, !rv, !sql,
   or !*sql.
*/
FSL_EXPORT int fsl_db_get_int32( fsl_db * db, int32_t * rv,
                      char const * sql, ... );

/**
   va_list counterpart of fsl_db_get_int32().
*/
FSL_EXPORT int fsl_db_get_int32v( fsl_db * db, int32_t * rv,
                       char const * sql, va_list args);

/**
   Convenience form of fsl_db_get_int32() which returns the value
   directly but provides no way of checking for errors. On error,
   or if no result is found, defaultValue is returned.
*/
FSL_EXPORT int32_t fsl_db_g_int32( fsl_db * db, int32_t defaultValue,
                            char const * sql, ... );

/**
   The int64 counterpart of fsl_db_get_int32(). See that function
   for the semantics.
*/
FSL_EXPORT int fsl_db_get_int64( fsl_db * db, int64_t * rv,
                      char const * sql, ... );

/**
   va_list counterpart of fsl_db_get_int64().
*/
FSL_EXPORT int fsl_db_get_int64v( fsl_db * db, int64_t * rv,
                       char const * sql, va_list args);

/**
   Convenience form of fsl_db_get_int64() which returns the value
   directly but provides no way of checking for errors. On error,
   or if no result is found, defaultValue is returned.
*/
FSL_EXPORT int64_t fsl_db_g_int64( fsl_db * db, int64_t defaultValue,
                            char const * sql, ... );


/**
   The fsl_id_t counterpart of fsl_db_get_int32(). See that function
   for the semantics.
*/
FSL_EXPORT int fsl_db_get_id( fsl_db * db, fsl_id_t * rv,
                   char const * sql, ... );

/**
   va_list counterpart of fsl_db_get_id().
*/
FSL_EXPORT int fsl_db_get_idv( fsl_db * db, fsl_id_t * rv,
                    char const * sql, va_list args);

/**
   Convenience form of fsl_db_get_id() which returns the value
   directly but provides no way of checking for errors. On error,
   or if no result is found, defaultValue is returned.
*/
FSL_EXPORT fsl_id_t fsl_db_g_id( fsl_db * db, fsl_id_t defaultValue,
                      char const * sql, ... );


/**
   The fsl_size_t counterpart of fsl_db_get_int32(). See that
   function for the semantics. If this function would fetch a
   negative value, it returns FSL_RC_RANGE and *rv is not modified.
*/
FSL_EXPORT int fsl_db_get_size( fsl_db * db, fsl_size_t * rv,
                     char const * sql, ... );

/**
   va_list counterpart of fsl_db_get_size().
*/
FSL_EXPORT int fsl_db_get_sizev( fsl_db * db, fsl_size_t * rv,
                      char const * sql, va_list args);

/**
   Convenience form of fsl_db_get_size() which returns the value
   directly but provides no way of checking for errors. On error,
   or if no result is found, defaultValue is returned.
*/
FSL_EXPORT fsl_size_t fsl_db_g_size( fsl_db * db, fsl_size_t defaultValue,
                          char const * sql, ... );


/**
   The double counterpart of fsl_db_get_int32(). See that function
   for the semantics.
*/
FSL_EXPORT int fsl_db_get_double( fsl_db * db, double * rv,
                                  char const * sql, ... );

/**
   va_list counterpart of fsl_db_get_double().
*/
FSL_EXPORT int fsl_db_get_doublev( fsl_db * db, double * rv,
                                   char const * sql, va_list args);

/**
   Convenience form of fsl_db_get_double() which returns the value
   directly but provides no way of checking for errors. On error,
   or if no result is found, defaultValue is returned.
*/
FSL_EXPORT double fsl_db_g_double( fsl_db * db, double defaultValue,
                                   char const * sql, ... );

/**
   The C-string counterpart of fsl_db_get_int32(). On success *rv
   will be set to a dynamically allocated string copied from the
   first column of the first result row. If rvLen is not NULL then
   *rvLen will be assigned the byte-length of that string. If no
   row is found, *rv is set to NULL and *rvLen (if not NULL) is set
   to 0, and 0 is returned. Note that NULL is also a legal result
   (an SQL NULL translates as a NULL string), The caller must
   eventually free the returned string value using fsl_free().
*/
FSL_EXPORT int fsl_db_get_text( fsl_db * db, char ** rv, fsl_size_t * rvLen,
                                char const * sql, ... );

/**
   va_list counterpart of fsl_db_get_text().
*/
FSL_EXPORT int fsl_db_get_textv( fsl_db * db, char ** rv, fsl_size_t * rvLen,
                      char const * sql, va_list args );

/**
   Convenience form of fsl_db_get_text() which returns the value
   directly but provides no way of checking for errors. On error,
   or if no result is found, NULL is returned. The returned string
   must eventually be passed to fsl_free() to free it.  If len is
   not NULL then if non-NULL is returned, *len will be assigned the
   byte-length of the returned string.
*/
FSL_EXPORT char * fsl_db_g_text( fsl_db * db, fsl_size_t * len,
                      char const * sql,
                      ... );

/**
   The Blob counterpart of fsl_db_get_text(). Identical to that
   function except that its output result (2nd paramter) type
   differs, and it fetches the data as a raw blob, without any sort
   of string interpretation. The returned *rv memory must
   eventually be passed to fsl_free() to free it. If len is not
   NULL then on success *len will be set to the byte length of the
   returned blob. If no row is found, *rv is set to NULL and *rvLen
   (if not NULL) is set to 0, and 0 is returned. Note that NULL is
   also a legal result (an SQL NULL translates as a NULL string),
*/
FSL_EXPORT int fsl_db_get_blob( fsl_db * db, void ** rv, fsl_size_t * len,
                     char const * sql, ... );


/**
   va_list counterpart of fsl_db_get_blob().
*/
FSL_EXPORT int fsl_db_get_blobv( fsl_db * db, void ** rv, fsl_size_t * stmtLen,
                      char const * sql, va_list args );

/**
   Convenience form of fsl_db_get_blob() which returns the value
   directly but provides no way of checking for errors. On error,
   or if no result is found, NULL is returned.
*/
FSL_EXPORT void * fsl_db_g_blob( fsl_db * db, fsl_size_t * len,
                      char const * sql,
                      ... );
/**
   Similar to fsl_db_get_text() and fsl_db_get_blob(), but writes
   its result to tgt, overwriting (not appennding to) any existing
   memory it might hold.

   If asBlob is true then the underlying BLOB API is used to
   populate the buffer, else the underlying STRING/TEXT API is
   used.  For many purposes there will be no difference, but if you
   know you might have binary data, be sure to pass a true value
   for asBlob to avoid any potential encoding-related problems.
*/
FSL_EXPORT int fsl_db_get_buffer( fsl_db * db, fsl_buffer * tgt,
                       char asBlob,
                       char const * sql, ... );

/**
   va_list counterpart of fsl_db_get_buffer().
*/
FSL_EXPORT int fsl_db_get_bufferv( fsl_db * db, fsl_buffer * tgt,
                        char asBlob,
                        char const * sql, va_list args );


/**
   Expects sql to be a SELECT-style query which (potentially)
   returns a result set. For each row in the set callback() is
   called, as described for fsl_stmt_each(). Returns 0 on success.
   The callback is _not_ called for queries which return no
   rows. If clients need to know if rows were returned, they can
   add a counter to their callbackState and increment it from the
   callback.

   Returns FSL_RC_MISUSE if !db, db is not opened, !callback,
   !sql. Returns FSL_RC_RANGE if !*sql.
*/
FSL_EXPORT int fsl_db_each( fsl_db * db, fsl_stmt_each_f callback,
                            void * callbackState, char const * sql, ... );

/**
   va_list counterpart to fsl_db_each().
*/
FSL_EXPORT int fsl_db_eachv( fsl_db * db, fsl_stmt_each_f callback,
                             void * callbackState, char const * sql, va_list args );


/**
   Returns the given Julian date value formatted as an ISO8601
   string (with a fractional seconds part if msPrecision is true,
   else without it).  Returns NULL if !db, db is not connected, j
   is less than 0, or on allocation error. The returned memory must
   eventually be freed using fsl_free().

   If localTime is true then the value is converted to the local time,
   otherwise it is not.

   @see fsl_db_unix_to_iso8601()
   @see fsl_julian_to_iso8601()
   @see fsl_iso8601_to_julian()
*/
FSL_EXPORT char * fsl_db_julian_to_iso8601( fsl_db * db, double j,
                                            char msPrecision, char localTime );

/**
   Returns the given Julian date value formatted as an ISO8601
   string (with a fractional seconds part if msPrecision is true,
   else without it).  Returns NULL if !db, db is not connected, j
   is less than 0, or on allocation error. The returned memory must
   eventually be freed using fsl_free().

   If localTime is true then the value is converted to the local time,
   otherwise it is not.

   @see fsl_db_julian_to_iso8601()
   @see fsl_julian_to_iso8601()
   @see fsl_iso8601_to_julian()
*/
FSL_EXPORT char * fsl_db_unix_to_iso8601( fsl_db * db, fsl_time_t j, char localTime );


/**
   Returns the current time in Julian Date format. Returns a negative
   value if !db or db is not opened.
*/
FSL_EXPORT double fsl_db_julian_now(fsl_db * db);

/**
   Uses the given db to convert the given time string to Julian Day
   format. If it cannot be converted, a negative value is returned.
   The str parameter can be anything suitable for passing to sqlite's:

   SELECT julianday(str)

   Note that this routine will escape str for use with SQL - the
   caller must not do so.

   @see fsl_julian_to_iso8601()
   @see fsl_iso8601_to_julian()
*/
FSL_EXPORT double fsl_db_string_to_julian(fsl_db * db, char const * str);

/**
   Opens the given db file and populates db with its handle.  db
   must have been cleanly initialized by copy-initializing it from
   fsl_db_empty (or fsl_db_empty_m) or by allocating it using
   fsl_db_malloc(). Failure to do so will lead to undefined
   behaviour.

   openFlags may be a mask of FSL_OPEN_F_xxx values, but not all
   are used/supported here. If FSL_OPEN_F_CREATE is _not_ set in
   openFlags and dbFile does not exist, it will return
   FSL_RC_NOT_FOUND. The existence of FSL_OPEN_F_CREATE in the
   flags will cause this routine to try to create the file if
   needed. If conflicting flags are specified (e.g. FSL_OPEN_F_RO
   and FSL_OPEN_F_RWC) then which one takes precedence is
   unspecified and possibly unpredictable.

   As a special case, if dbFile is ":memory:" (for an in-memory
   database) or "" (empty string, for a "temporary" database) then it
   is is passed through without any filesystem-related checks and the
   openFlags are ignored.

   See this page for the differences between ":memory:" and "":

   https://www.sqlite.org/inmemorydb.html

   Returns FSL_RC_MISUSE if !db, !dbFile, !*dbFile, or if db->dbh
   is not NULL (i.e. if it is already opened or its memory was
   default-initialized (use fsl_db_empty to cleanly copy-initialize
   new stack-allocated instances).

   On error db->dbh will be NULL, but db->error might contain error
   details.

   Regardless of success or failure, db should be passed to
   fsl_db_close() to free up all memory associated with it. It is
   not closed automatically by this function because doing so cleans
   up the error state, which the caller will presumably want to
   have.

   If db->f is not NULL when this is called then it is assumed that
   db should be plugged in to the Fossil repository system, and the
   following additional things happen:

   - A number of SQL functions are registered with the db. Details
   are below.

   - If FSL_OPEN_F_SCHEMA_VALIDATE is set in openFlags then the
   db is validated to see if it has a fossil schema.  If that
   validation fails, FSL_RC_REPO_NEEDS_REBUILD or FSL_RC_NOT_A_REPO
   will be returned and db's error state will be updated. db->f
   does not need to be set for that check to work.


   The following SQL functions get registered with the db if db->f
   is not NULL when this function is called:

   - NOW() returns the current time as an integer, as per time(2).

   - FSL_USER() returns the current value of fsl_cx_user_get(),
   or NULL if that is not set.

   - FSL_CONTENT(INTEGER|STRING) returns the undeltified,
   uncompressed content for the blob record with the given ID (if
   the argument is an integer) or symbolic name (as per
   fsl_sym_to_rid()), as per fsl_content_get(). If the argument
   does not resolve to an in-repo blob, a db-level error is
   triggered. If passed an integer, no validation is done on its
   validity, but such checking can be enforced by instead passing
   the the ID as a string in the form "rid:ID". Both cases will
   result in an error if the RID is not found, but the error
   reporting is arguably slightly better for the "rid:ID" case.

   - FSL_SYM2RID(STRING) returns a blob RID for the given symbol,
   as per fsl_sym_to_rid(). Triggers an SQL error if fsl_sym_to_rid()
   fails.

   - FSL_DIRPART(STRING[, BOOL=0]) behaves like fsl_file_dirpart(),
   returning the result as a string unless it is empty, in which case
   the result is an SQL NULL.

   Note that functions described as "triggering a db error" will
   propagate that error, such that fsl_db_err_get() can report it
   to the client.


   @see fsl_db_close()
   @see fsl_db_prepare()
   @see fsl_db_malloc()
*/
FSL_EXPORT int fsl_db_open( fsl_db * db, char const * dbFile, int openFlags );

/**
   Closes the given db handle and frees any resources owned by
   db. Returns 0 on success.

   If db was allocated using fsl_db_malloc() (as determined by
   examining db->allocStamp) then this routine also fsl_free()s it,
   otherwise it is assumed to either be on the stack or part of a
   larger struct and is not freed.

   If db has any pending transactions, they are rolled
   back by this function.
*/
FSL_EXPORT int fsl_db_close( fsl_db * db );

/**
   If db is an opened db handle, this registers a debugging
   function with the db which traces all SQL to the given FILE
   handle (defaults to stdout if outStream is NULL).

   This mechanism is only intended for debugging and exploration of
   how Fossil works. Tracing is often as easy way to ensure that a
   given code block is getting run.

   As a special case, if db->f is not NULL _before_ it is is
   fsl_db_open()ed, then this function automatically gets installed
   if the SQL tracing option is enabled for that fsl_cx instance
   before the db is opened.

   This is a no-op if !db or db is not opened.
*/
FSL_EXPORT void fsl_db_sqltrace_enable( fsl_db * db, FILE * outStream );

/**
   Returns the row ID of the most recent insertion,
   or -1 if !db, db is not connected, or 0 if no inserts
   have been performed.
*/
FSL_EXPORT fsl_id_t fsl_db_last_insert_id(fsl_db *db);

/**
   Returns non-0 (true) if the database (which must be open) table
   identified by zTableName has a column named zColName
   (case-sensitive), else returns 0.
*/
FSL_EXPORT char fsl_db_table_has_column( fsl_db * db, char const *zTableName,
                              char const *zColName );

/**
   If a db name has been associated with db then it is returned,
   otherwise NULL is returned. A db has no name by default, but
   fsl_cx-used ones get their database name assigned to them
   (e.g. "main" for the main db).
*/
FSL_EXPORT char const * fsl_db_name(fsl_db const * db);

  
/**
   Returns a db name string for the given fsl_db_role value. The
   string is static, guaranteed to live as long as the app.  It
   returns NULL (or asserts in debug builds) if passed
   FSL_DBROLE_NONE or some value out of range for the enum.
*/
FSL_EXPORT const char * fsl_db_role_label(enum fsl_dbrole_e r);


/**
   Allocates a new fsl_db instance(). Returns NULL on allocation
   error. Note that fsl_db instances can often be used from the
   stack - allocating them dynamically is an uncommon case necessary
   for script bindings.

   Achtung: the returned value's allocStamp member is used for
   determining if fsl_db_close() should free the value or not.  Thus
   if clients copy over this value without adjusting allocStamp back
   to its original value, the library will likely leak the instance.
   Been there, done that.
*/
FSL_EXPORT fsl_db * fsl_db_malloc();

/**
   The fsl_stmt counterpart of fsl_db_malloc(). See that function
   for when you might want to use this and a caveat involving the
   allocStamp member of the returned value. fsl_stmt_finalize() will
   free statements created with this function.
*/
FSL_EXPORT fsl_stmt * fsl_stmt_malloc();


/**
   ATTACHes the file zDbName to db using the databbase name
   zLabel. Returns 0 on success. Returns FSL_RC_MISUSE if any
   argument is NULL or any string argument starts with a NUL byte,
   else it returns the result of fsl_db_exec() which attaches the
   db. On db-level errors db's error state will be updated.
*/
FSL_EXPORT int fsl_db_attach(fsl_db * db, const char *zDbName, const char *zLabel);

/**
   The converse of fsl_db_detach(). Must be passed the same arguments
   which were passed as the 1st and 3rd arguments to fsl_db_attach().
   Returns 0 on success, FSL_RC_MISUSE if !db, !zLabel, or !*zLabel,
   else it returns the result of the underlying fsl_db_exec()
   call.
*/
FSL_EXPORT int fsl_db_detach(fsl_db * db, const char *zLabel);


/**
   Expects fmt to be a SELECT-style query. For each row in the
   query, the first column is fetched as a string and appended to
   the tgt list.

   Returns 0 on success, FSL_RC_MISUSE if !db, !tgt, or !fmt, any
   number of potential FSL_RC_OOM or db-related errors.

   Results rows with a NULL value (resulting from an SQL NULL) are
   added to the list as NULL entries.

   Each entry appended to the list is a (char *) which must
   be freed using fsl_free(). To easiest way to clean up
   the list and its contents is:

   @code
   fsl_list_visit_free(tgt,...);
   @endcode

   On error the list may be partially populated.

   Complete example:
   @code
   fsl_list li = fsl_list_empty;
   int rc = fsl_db_select_slist(db, &li,
   "SELECT uuid FROM blob WHERE rid<20");
   if(!rc){
   fsl_size_t i;
   for(i = 0;i < li.used; ++i){
   char const * uuid = (char const *)li.list[i];
   fsl_fprintf(stdout, "UUID: %s\n", uuid);
   }
   }
   fsl_list_visit_free(&li, 1);
   @endcode

   Of course fsl_list_visit() may be used to traverse the list as
   well, as long as the visitor expects (char [const]*) list
   elements.
*/
FSL_EXPORT int fsl_db_select_slist( fsl_db * db, fsl_list * tgt,
                         char const * fmt, ... );

/**
   The va_list counterpart of fsl_db_select_slist().
*/
FSL_EXPORT int fsl_db_select_slistv( fsl_db * db, fsl_list * tgt,
                          char const * fmt, va_list args );

/**
   Returns n bytes of random lower-case hexidecimal characters
   using the given db as its data source, plus a terminating NUL
   byte. The returned memory must eventually be freed using
   fsl_free(). Returns NULL if !db, !n, or on a db-level error.
*/
FSL_EXPORT char * fsl_db_random_hex(fsl_db * db, fsl_size_t n);

/**
   Returns the "number of database rows that were changed or
   inserted or deleted by the most recently completed SQL statement"
   (to quote the underlying APIs). Returns 0 if !db or if db is not
   opened.


   See: https://sqlite.org/c3ref/changes.html
*/
FSL_EXPORT int fsl_db_changes_recent(fsl_db * db);
  
/**
   Returns "the number of row changes caused by INSERT, UPDATE or
   DELETE statements since the database connection was opened" (to
   quote the underlying APIs). Returns 0 if !db or if db is not
   opened.

   See; https://sqlite.org/c3ref/total_changes.html
*/
FSL_EXPORT int fsl_db_changes_total(fsl_db * db);

/**
   Initializes the given database file. zFilename is the name of
   the db file. It is created if needed, but any directory
   components are not created. zSchema is the base schema to
   install.  The following arguments may be (char const *) SQL
   code, each of which gets run against the db after the main
   schema is called.  The variadic argument list MUST end with NULL
   (0), even if there are no non-NULL entries.

   Returns 0 on success.

   On error, if err is not NULL then it is populated with any error
   state from the underlying (temporary) db handle.
*/
FSL_EXPORT int fsl_db_init( fsl_error * err, char const * zFilename,
                 char const * zSchema, ... );

/**
   A fsl_stmt_each_f() impl, intended primarily for debugging, which
   simply outputs row data in tabular form via fsl_output(). The
   state argument is ignored. This only works if stmt was prepared
   by a fsl_db instance which has an associated fsl_cx instance. On
   the first row, the column names are output.
*/
FSL_EXPORT int fsl_stmt_each_f_dump( fsl_stmt * stmt, void * state );

/**
   Returns true if the table name specified by the final argument
   exists in the fossil database specified by the 2nd argument on the
   db connection specified by the first argument, else returns false.

   Potential TODO: this is a bit of a wonky interface. Consider
   changing it to eliminate the role argument, which is only really
   needed if we have duplicate table names across attached dbs or if
   we internally mess up and write a table to the wrong db.
*/
FSL_EXPORT bool fsl_db_table_exists(fsl_db * db, fsl_dbrole_e whichDb, const char *zTable);

/**
   The elipsis counterpart of fsl_stmt_bind_fmtv().
*/
FSL_EXPORT int fsl_stmt_bind_fmt( fsl_stmt * st, char const * fmt, ... );

/**
    Binds a series of values using a formatting string.
   
    The string may contain the following characters, each of which
    refers to the next argument in the args list:
   
    '-': binds a NULL and expects a NULL placeholder
    in the argument list (for consistency's sake).
   
    'i': binds an int32
   
    'I': binds an int64
   
    'R': binds a fsl_id_t ('R' as in 'RID')
   
    'f': binds a double
   
    's': binds a (char const *) as text or NULL.
   
    'S': binds a (char const *) as a blob or NULL.
   
    'b': binds a (fsl_buffer const *) as text or NULL.
   
    'B': binds a (fsl_buffer const *) as a blob or NULL.
   
    ' ': spaces are allowed for readability and are ignored.

    Returns 0 on success, any number of other FSL_RC_xxx codes on
    error.

    ACHTUNG: the "sSbB" bindings assume, because of how this API is
    normally used, that the memory pointed to by the given argument
    will outlive the pending step of the given statement, so that
    memory is NOT copied by the binding. Thus results are undefined if
    such an argument's memory is invalidated before the statement is
    done with it.
*/
FSL_EXPORT int fsl_stmt_bind_fmtv( fsl_stmt * st, char const * fmt,
                                   va_list args );

/**
   Works like fsl_stmt_bind_fmt() but:

   1) It calls fsl_stmt_reset() before binding the arguments.

   2) If binding succeeds then it steps the given statement a single
   time.

   3) If the result is NOT FSL_RC_STEP_ROW then it also resets the
   statement before returning. It does not do so for FSL_RC_STEP_ROW
   because doing so would remove the fetched columns (and this is why
   it resets in step (1)).

   Returns 0 if stepping results in FSL_RC_STEP_DONE, FSL_RC_STEP_ROW
   if it produces a result row, or any number of other potential non-0
   codes on error. On error, the error state of st->db is updated.

   Design note: the return value for FSL_RC_STEP_ROW, as opposed to
   returning 0, is necessary for proper statement use if the client
   wants to fetch any result data from the statement afterwards (which
   is illegal if FSL_RC_STEP_ROW was not the result). This is also why
   it cannot reset the statement if that result is returned.
*/
FSL_EXPORT int fsl_stmt_bind_stepv( fsl_stmt * st, char const * fmt,
                                    va_list args );

/**
   The elipsis counterpart of fsl_stmt_bind_stepv().
*/
FSL_EXPORT int fsl_stmt_bind_step( fsl_stmt * st, char const * fmt, ... );

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






















































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted include/fossil-scm/fossil-forum.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* -*- 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_FORUM_H_INCLUDED)
#define ORG_FOSSIL_SCM_FSL_FORUM_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 public APIs for working with fossil-managed content.
*/

#include "fossil-core.h" /* MUST come first b/c of config macros */

/**
   If the given fossil context has a db opened, this function
   installs, if needed, the forum-related schema and returns 0 on
   success (or if no installation was needed). If f has no repository
   opened, FSL_RC_NOT_A_REPO is returned. Some other FSL_RC_xxx value
   is returned if there is a db-level error during installation.
*/
int fsl_repo_install_schema_forum(fsl_cx *f);


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






































































Deleted include/fossil-scm/fossil-hash.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
/* -*- 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_HASH_H_INCLUDED)
#define ORG_FOSSIL_SCM_FSL_HASH_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 public APIs relating to hashing.
*/

#include "fossil-util.h" /* MUST come first b/c of config macros */
#if !defined(FSL_SHA1_HARDENED)
#  define FSL_SHA1_HARDENED 1
#endif
#if defined(__cplusplus)
extern "C" {
#endif

/**
   Various set-in-stone constants used by the API.
*/
enum fsl_hash_constants {
/**
   The length, in bytes, of fossil's hex-form SHA1 UUID strings.
*/
FSL_STRLEN_SHA1 = 40,
/**
   The length, in bytes, of fossil's hex-form SHA3-256 UUID strings.
*/
FSL_STRLEN_K256 = 64,
/**
   The length, in bytes, of a hex-form MD5 hash.
*/
FSL_STRLEN_MD5 = 32,

/** Minimum length of a full UUID. */
FSL_UUID_STRLEN_MIN = FSL_STRLEN_SHA1,
/** Maximum length of a full UUID. */
FSL_UUID_STRLEN_MAX = FSL_STRLEN_K256
};

/**
   Unique IDs for artifact hash types supported by fossil.
*/
enum fsl_hash_types_e {
/** Invalid hash type. */
FSL_HTYPE_ERROR = 0,
/** SHA1. */
FSL_HTYPE_SHA1 = 1,
/** SHA3-256. */
FSL_HTYPE_K256 = 2
};
typedef enum fsl_hash_types_e fsl_hash_types_e;

typedef struct fsl_md5_cx fsl_md5_cx;
typedef struct fsl_sha1_cx fsl_sha1_cx;
typedef struct fsl_sha3_cx fsl_sha3_cx;

/**
   The hash string of the initial MD5 state. Used as an
   optimization for some places where we need an MD5 but know it
   will not hash any data.

   Equivalent to what the md5sum command outputs for empty input:

   @code
   # md5sum < /dev/null
   d41d8cd98f00b204e9800998ecf8427e  -
   @endcode
*/
#define FSL_MD5_INITIAL_HASH "d41d8cd98f00b204e9800998ecf8427e"

/**
   Holds state for MD5 calculations. It is intended to be used like
   this:

   @code
   unsigned char digest[16];
   char hex[FSL_STRLEN_MD5+1];
   fsl_md5_cx cx = fsl_md5_cx_empty;
   // alternately: fsl_md5_init(&cx);
   ...call fsl_md5_update(&cx,...) any number of times to
   ...incrementally calculate the hash.
   fsl_md5_final(&cx, digest); // ends the calculation
   fsl_md5_digest_to_base16(digest, hex);
   // digest now contains the raw 16-byte MD5 digest.
   // hex now contains the 32-byte MD5 + a trailing NUL
   @endcode
*/
struct fsl_md5_cx {
  int isInit;
  uint32_t buf[4];
  uint32_t bits[2];
  unsigned char in[64];
};
#define fsl_md5_cx_empty_m {                                  \
    1/*isInit*/,                                              \
    {/*buf*/0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 }, \
    {/*bits*/0,0},                                            \
    {0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,                \
     0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,                \
     0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,                \
     0,0,0,0}}

/**
   A fsl_md5_cx instance which holds the initial state
   used for md5 calculations. Instances must either be
   copy-initialized from this instance or they must be
   passed to fsl_md5_init() before they are used.
*/
FSL_EXPORT const fsl_md5_cx fsl_md5_cx_empty;

/**
   Initializes the given context pointer. It must not be NULL.  This
   must be the first routine called on any fsl_md5_cx instances.
   Alternately, copy-constructing fsl_md5_cx_empty has the same effect.

   @see fsl_md5_update()
   @see fsl_md5_final()
*/
FSL_EXPORT void fsl_md5_init(fsl_md5_cx *cx);

/**
   Updates cx's state to reflect the addition of the data
   specified by the range (buf, buf+len]. Neither cx nor buf may
   be NULL. This may be called an arbitrary number of times between
   fsl_md5_init() and fsl_md5_final().

   @see fsl_md5_init()
   @see fsl_md5_final()
*/
FSL_EXPORT void fsl_md5_update(fsl_md5_cx *cx, void const * buf, fsl_size_t len);

/**
   Finishes up the calculation of the md5 for the given context and
   writes a 16-byte digest value to the 2nd parameter.  Use
   fsl_md5_digest_to_base16() to convert the digest output value to
   hexidecimal form.

   @see fsl_md5_init()
   @see fsl_md5_update()
   @see fsl_md5_digest_to_base16()
*/
FSL_EXPORT void fsl_md5_final(fsl_md5_cx * cx, unsigned char * digest);

/**
   Converts an md5 digest value (from fsl_md5_final()'s 2nd
   parameter) to a 32-byte (FSL_STRLEN_MD5) CRC string plus a
   terminating NUL byte. i.e.  zBuf must be at least
   (FSL_STRLEN_MD5+1) bytes long.

   @see fsl_md5_final()
*/
FSL_EXPORT void fsl_md5_digest_to_base16(unsigned char *digest, char *zBuf);

/**
   The md5 counterpart of fsl_sha1sum_buffer(), identical in
   semantics except that its result is an MD5 hash instead of an
   SHA1 hash and the resulting hex string is FSL_STRLEN_MD5 bytes
   long plus a terminating NUL.
*/
FSL_EXPORT int fsl_md5sum_buffer(fsl_buffer const *pIn, fsl_buffer *pCksum);

/**
   The md5 counterpart of fsl_sha1sum_cstr(), identical in
   semantics except that its result is an MD5 hash instead of an
   SHA1 hash and the resulting string is FSL_STRLEN_MD5 bytes long
   plus a terminating NUL.
*/
FSL_EXPORT char *fsl_md5sum_cstr(const char *zIn, fsl_int_t len);

/**
   The MD5 counter part to fsl_sha1sum_stream(), with identical
   semantics except that the generated hash is an MD5 string
   instead of SHA1.
*/
FSL_EXPORT int fsl_md5sum_stream(fsl_input_f src, void * srcState, fsl_buffer *pCksum);

/**
   Reads all input from src() and passes it through fsl_md5_update(cx,...).
   Returns 0 on success, FSL_RC_MISUSE if !cx or !src. If src returns
   a non-0 code, that code is returned from here.
*/
FSL_EXPORT int fsl_md5_update_stream(fsl_md5_cx *cx, fsl_input_f src, void * srcState);

/**
   Equivalent to fsl_md5_update(cx, b->mem, b->used). Results are undefined
   if either pointer is invalid or NULL.
*/
FSL_EXPORT void fsl_md5_update_buffer(fsl_md5_cx *cx, fsl_buffer const * b);

/**
   Passes the first len bytes of str to fsl_md5_update(cx). If len
   is less than 0 then fsl_strlen() is used to calculate the
   length.  Results are undefined if either pointer is invalid or
   NULL. This is a no-op if !len or (len<0 && !*str).
*/
FSL_EXPORT void fsl_md5_update_cstr(fsl_md5_cx *cx, char const * str, fsl_int_t len);

/**
   A fsl_md5_update_stream() proxy which updates cx to include the
   contents of the given file.
*/
FSL_EXPORT int fsl_md5_update_filename(fsl_md5_cx *cx, char const * fname);

/**
   The MD5 counter part to fsl_sha1sum_filename(), with identical
   semantics except that the generated hash is an MD5 string
   instead of SHA1.
*/
FSL_EXPORT int fsl_md5sum_filename(const char *zFilename, fsl_buffer *pCksum);


#if FSL_SHA1_HARDENED
typedef void(*fsl_sha1h_collision_callback)(uint64_t, const uint32_t*, const uint32_t*, const uint32_t*, const uint32_t*);
#endif
/**
   Holds state for SHA1 calculations. It is intended to be used
   like this:

   @code
   unsigned char digest[20]
   char hex[FSL_STRLEN_SHA1+1];
   fsl_sha1_cx cx = fsl_sha1_cx_empty;
   // alternately: fsl_sha1_init(&cx)
   ...call fsl_sha1_update(&cx,...) any number of times to
   ...incrementally calculate the hash.
   fsl_sha1_final(&cx, digest); // ends the calculation
   fsl_sha1_digest_to_base16(digest, hex);
   // digest now contains the raw 20-byte SHA1 digest.
   // hex now contains the 40-byte SHA1 + a trailing NUL
   @endcode
*/
struct fsl_sha1_cx {
#if FSL_SHA1_HARDENED
  uint64_t total;
  uint32_t ihv[5];
  unsigned char buffer[64];
  int bigendian;
  int found_collision;
  int safe_hash;
  int detect_coll;
  int ubc_check;
  int reduced_round_coll;
  fsl_sha1h_collision_callback callback;
  uint32_t ihv1[5];
  uint32_t ihv2[5];
  uint32_t m1[80];
  uint32_t m2[80];
  uint32_t states[80][5];
#else
  unsigned int state[5];
  unsigned int count[2];
  unsigned char buffer[64];
#endif
};
/**
   fsl_sha1_cx instance intended for in-struct copy initialization.
*/
#if FSL_SHA1_HARDENED
#define fsl_sha1_cx_empty_m {0}
#else
#define fsl_sha1_cx_empty_m {                                           \
    {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0 },      \
    {0,0},                                                              \
    {0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0, \
        0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0 \
        }                                                               \
  }
#endif
/**
   fsl_sha1_cx instance intended for copy initialization. For build
   config portability, the copied-to object must still be passed to
   fsl_sha1_init() to initialize it.
*/
FSL_EXPORT const fsl_sha1_cx fsl_sha1_cx_empty;

/**
   Initializes the given context with the initial SHA1 state.  This
   must be the first routine called on an SHA1 context, and passing
   this context to other SHA1 routines without first having passed
   it to this will lead to undefined results.

   @see fsl_sha1_update()
   @see fsl_sha1_final()
*/
FSL_EXPORT void fsl_sha1_init(fsl_sha1_cx *context);

/**
   Updates the given context to include the hash of the first len
   bytes of the given data.

   @see fsl_sha1_init()
   @see fsl_sha1_final()
*/
FSL_EXPORT void fsl_sha1_update( fsl_sha1_cx *context, void const *data, fsl_size_t len);

/**
   Add padding and finalizes the message digest. If digest is not NULL
   then it writes 20 bytes of digest to the 2nd parameter. If this
   library is configured with hardened SHA1 hashes, this function
   returns non-0 if a collision was detected while hashing. If it is
   not configured for hardened SHA1, or no collision was detected, it
   returns 0.

   @see fsl_sha1_update()
   @see fsl_sha1_digest_to_base16()
*/
FSL_EXPORT int fsl_sha1_final(fsl_sha1_cx *context, unsigned char * digest);

/**
   A convenience form of fsl_sha1_final() which writes
   FSL_STRLEN_SHA1+1 bytes (hash plus terminating NUL byte) to the
   2nd argument and returns a (const char *)-type cast of the 2nd
   argument.
*/
FSL_EXPORT const char * fsl_sha1_final_hex(fsl_sha1_cx *context, char * zHex);

/**
   Convert a digest into base-16.  digest must be at least 20 bytes
   long and hold an SHA1 digest. zBuf must be at least (FSL_STRLEN_SHA1
   + 1) bytes long, to which FSL_STRLEN_SHA1 characters of
   hexidecimal-form SHA1 hash and 1 NUL byte will be written.

   @see fsl_sha1_final()
*/
FSL_EXPORT void fsl_sha1_digest_to_base16(unsigned char *digest, char *zBuf);

/**
   Computes the SHA1 checksum of pIn and stores the resulting
   checksum in the buffer pCksum.  pCksum's memory is re-used if is
   has any allocated to it. pCksum may == pIn, in which case this
   is a destructive operation (replacing the hashed data with its
   hash code).

   Return 0 on success, FSL_RC_OOM if (re)allocating pCksum fails.
*/
FSL_EXPORT int fsl_sha1sum_buffer(fsl_buffer const *pIn, fsl_buffer *pCksum);

/**
   Computes the SHA1 checksum of the first len bytes of the given
   string.  If len is negative then zIn must be NUL-terminated and
   fsl_strlen() is used to find its length. The result is a
   FSL_UUID_STRLEN-byte string (+NUL byte) returned in memory
   obtained from fsl_malloc(), so it must be passed to fsl_free()
   to free it. If NULL==zIn or !len then NULL is returned.
*/
FSL_EXPORT char *fsl_sha1sum_cstr(const char *zIn, fsl_int_t len);

/**
   Consumes all input from src and calculates its SHA1 hash. The
   result is set in pCksum (its contents, if any, are overwritten,
   not appended to). Returns 0 on success. Returns FSL_RC_MISUSE if
   !src or !pCksum. It keeps consuming input from src() until that
   function reads fewer bytes than requested, at which point EOF is
   assumed. If src() returns non-0, that code is returned from this
   function.
*/
FSL_EXPORT int fsl_sha1sum_stream(fsl_input_f src, void * srcState, fsl_buffer *pCksum);


/**
   A fsl_sha1sum_stream() wrapper which calculates the SHA1 of
   given file.

   Returns FSL_RC_IO if the file cannot be opened, FSL_RC_MISUSE if
   !zFilename or !pCksum, else as per fsl_sha1sum_stream().

   TODO: the v1 impl has special behaviour for symlinks which this
   function lacks. For that support we need a variant of this
   function which takes a fsl_cx parameter (for the allow-symlinks
   setting).
*/
FSL_EXPORT int fsl_sha1sum_filename(const char *zFilename, fsl_buffer *pCksum);

/**
   Legal values for SHA3 hash sizes, in bits: an increment of 32 bits
   in the inclusive range (128..512).

   The hexidecimal-code size, in bytes, of any given bit size in this
   enum is the bit size/4.
*/
enum fsl_sha3_hash_size {
/** Sentinel value. Must be 0. */
FSL_SHA3_INVALID = 0,
FSL_SHA3_128 = 128, FSL_SHA3_160 = 160, FSL_SHA3_192 = 192,
FSL_SHA3_224 = 224, FSL_SHA3_256 = 256, FSL_SHA3_288 = 288,
FSL_SHA3_320 = 320, FSL_SHA3_352 = 352, FSL_SHA3_384 = 384,
FSL_SHA3_416 = 416, FSL_SHA3_448 = 448, FSL_SHA3_480 = 480,
FSL_SHA3_512 = 512,
/* Default SHA3 flavor */
FSL_SHA3_DEFAULT = 256
};

/**
   Type for holding SHA3 processing state. Each instance must be
   initialized with fsl_sha3_init(), populated with fsl_sha3_update(),
   and "sealed" with fsl_sha3_end().

   Sample usage:

   @code
   fsl_sha3_cx cx;
   fsl_sha3_init(&cx, FSL_SHA3_DEFAULT);
   fsl_sha3_update(&cx, memory, lengthOfMemory);
   fsl_sha3_end(&cx);
   printf("Hash = %s\n", (char const *)cx.hex);
   @endcode

   After fsl_sha3_end() is called cx.hex contains the hex-string forms
   of the digest. Note that fsl_sha3_update() may be called an arbitrary
   number of times to feed in chunks of memory (e.g. to stream in
   arbitrarily large data).
*/
struct fsl_sha3_cx {
    union {
        uint64_t s[25];         /* Keccak state. 5x5 lines of 64 bits each */
        unsigned char x[1600];  /* ... or 1600 bytes */
    } u;
    unsigned nRate;        /* Bytes of input accepted per Keccak iteration */
    unsigned nLoaded;      /* Input bytes loaded into u.x[] so far this cycle */
    unsigned ixMask;       /* Insert next input into u.x[nLoaded^ixMask]. */
    enum fsl_sha3_hash_size size; /* Size of the hash, in bits. */
    unsigned char hex[132]; /* Hex form of final digest: 56-128 bytes
                               plus terminating NUL. */
};

/**
   If the given number is a valid fsl_sha3_hash_size value, its enum
   entry is returned, else FSL_SHA3_INVALID is returned.

   @see fsl_sha3_init()
*/
FSL_EXPORT enum fsl_sha3_hash_size fsl_sha3_hash_size_for_int(int);

/**
   Initialize a new hash. The second argument specifies the size of
   the hash in bits. Results are undefined if cx is NULL or sz is not
   a valid positive value.

   After calling this, use fsl_sha3_update() to hash data and
   fsl_sha3_end() to finalize the hashing process and generate a digest.
*/
FSL_EXPORT void fsl_sha3_init2(fsl_sha3_cx *cx, enum fsl_sha3_hash_size sz);

/**
   Equivalent to fsl_sha3_init2(cx, FSL_SHA3_DEFAULT).
*/
FSL_EXPORT void fsl_sha3_init(fsl_sha3_cx *cx);

/**
   Updates cx's state to include the first len bytes of data.

   If cx is NULL results are undefined (segfault!). If mem is not
   NULL then it must be at least n bytes long. If n is 0 then this
   function has no side-effects.

   @see fsl_sha3_init()
   @see fsl_sha3_end()
*/
FSL_EXPORT void fsl_sha3_update( fsl_sha3_cx *cx, void const *data, unsigned int len);

/**
   To be called when SHA3 hashing is complete: finishes the hash
   calculation and populates cx->hex with the final hash code in
   hexidecimal-string form. Returns the binary-form digest value,
   which refers to cx->size/8 bytes of memory which lives in the cx
   object. After this call cx->hex will be populated with cx->size/4
   bytes of lower-case ASCII hex codes plus a terminating NUL byte.

   Potential TODO: change fsl_sha1_final() and fsl_md5_final() to use
   these same return semantics.

   @see fsl_sha3_init()
   @see fsl_sha3_update()
*/
FSL_EXPORT unsigned char const * fsl_sha3_end(fsl_sha3_cx *cx);

/**
   SHA3-256 counterpart of fsl_sha1_digest_to_base16(). digest must be at least
   32 bytes long and hold an SHA3 digest. zBuf must be at least (FSL_STRLEN_K256+1)
   bytes long, to which FSL_STRLEN_K256 characters of
   hexidecimal-form SHA3 hash and 1 NUL byte will be written

   @see fsl_sha3_end().
*/
FSL_EXPORT void fsl_sha3_digest_to_base16(unsigned char *digest, char *zBuf);
/**
   SHA3 counter part of fsl_sha1sum_buffer().
*/
FSL_EXPORT int fsl_sha3sum_buffer(fsl_buffer const *pIn, fsl_buffer *pCksum);
/**
   SHA3 counter part of fsl_sha1sum_cstr().
*/
FSL_EXPORT char *fsl_sha3sum_cstr(const char *zIn, fsl_int_t len);
/**
   SHA3 counterpart of fsl_sha1sum_stream().
 */
FSL_EXPORT int fsl_sha3sum_stream(fsl_input_f src, void * srcState, fsl_buffer *pCksum);
/**
   SHA3 counterpart of fsl_sha1sum_filename().
 */
FSL_EXPORT int fsl_sha3sum_filename(const char *zFilename, fsl_buffer *pCksum);

/**
   Expects zHash to be a full-length hash value of one of the
   fsl_hash_types_t-specified types, and nHash to be the length, in
   bytes, of zHash's contents (which must be the full hash length, not
   a prefix). If zHash can be validated as a hash, its corresponding
   hash type is returned, else FSL_HTYPE_ERROR is returned.
*/
FSL_EXPORT fsl_hash_types_e fsl_validate_hash(const char *zHash, int nHash);

/**
   Expects (zHash, nHash) to refer to a full hash (of a supported
   content hash type) of pIn's contents. This routine hashes pIn's
   contents and, if it compares equivalent to zHash then the ID of the
   hash type is returned.  On a mismatch, FSL_HTYPE_ERROR is returned.
*/
FSL_EXPORT fsl_hash_types_e fsl_verify_blob_hash(fsl_buffer const * pIn,
                                                 const char *zHash, int nHash);

/**
   Returns a human-readable name for the given hash type, or its
   second argument h is not a supported hash type.
 */
FSL_EXPORT const char * fsl_hash_type_name(fsl_hash_types_e h, const char *zUnknown);

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


























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted include/fossil-scm/fossil-internal.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
/* -*- 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 "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 buffer memory (in bytes) used by cached content.
     This does not account for memory held by this->list.
  */
  fsl_size_t 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.
  */
  fsl_size_t 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 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_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*/,                             \
  20000000/*szLimit. Historical fossil value=50M*/, \
  0/*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*/           \
}
/** @internal

    Empty-initialized fsl_acache structure, intended
    for copy initialization.
*/
extern const fsl_acache fsl_acache_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. */
  unsigned nextAge;
  /** The virtual age of each deck in the cache. They get evicted
      oldest first. */
  unsigned aAge[4];
  /** Counts the number of cache hits. */
  unsigned hits;
  /** Counts the number of cache misses. */
  unsigned misses;
  /**
     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-03-26) 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,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.

     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_ckout(), 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_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.
    */
    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;

    /**
       Julian mtime of the checkout version, as reported by the
       [event] table.
    */
    double mtime;
  } 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 {
    /**
       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.
  */
  fsl_confirmer confirmer;

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

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

  /**
     Reuseable scratchpads for low-level file canonicalization
     buffering and whatnot. Not intended for huge content: use
     this->fileContent for that. This list must stay relatively
     short. As of this writing, most buffers ever active at once was
     5, but 1-2 is more common.

     @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 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 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
       (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.
    */
    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;
    
    /**
       Artifact cache used during processing of manifests.
    */
    fsl_acache arty;
    /**
       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 (2021-03)
       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 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 _generall_ 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;
  } 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*/, 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*/,                  \
    fsl_buffer_empty_m /*fileContent*/,           \
    {/*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*/,                   \
    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*/,                  \
      -1/*manifestSetting*/,\
      0/*rcvId*/,                               \
      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*/,                    \
      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*/            \
      }                                     \
    }/*cache*/,                             \
    {/*ticket*/                             \
      fsl_list_empty_m/*customFields*/,     \
      0/*hasTicket*/,                       \
      0/*hasCTime*/,                        \
      0/*hasChng*/,                         \
      0/*hasCngRid*/                        \
    }                                       \
  }

/** @internal
    Initialized-with-defaults fsl_cx instance.
*/
FSL_EXPORT 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.
*/
FSL_EXPORT 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 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 returned 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.
*/
FSL_EXPORT 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.
*/
FSL_EXPORT 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.
*/
FSL_EXPORT 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 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()
*/
FSL_EXPORT 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()
*/
FSL_EXPORT 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()
*/
FSL_EXPORT 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()
*/
FSL_EXPORT 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()).
*/
FSL_EXPORT int fsl_content_new( fsl_cx * f, fsl_uuid_cstr uuid, bool isPrivate,
                                fsl_id_t * 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.
*/
FSL_EXPORT 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.
*/
FSL_EXPORT 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.
*/
FSL_EXPORT 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.
*/
FSL_EXPORT 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 artifact pid to the children of
    pid. Returns 0 on... non-error. Returns FSL_RC_RANGE if pid<=0.
*/
FSL_EXPORT 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. 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.
*/
FSL_EXPORT 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.
*/
FSL_EXPORT 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).
*/
FSL_EXPORT 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.
*/
FSL_EXPORT 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.
*/
FSL_EXPORT void fsl_db_clear_strings(fsl_db * 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.
*/
FSL_EXPORT 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_EXPORT 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()
*/
FSL_EXPORT 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()
*/
FSL_EXPORT 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()
*/
FSL_EXPORT 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.
*/
FSL_EXPORT 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.

    Reminder to self: this is part of the infrastructure for copying
    config state from an existing repo when creating new repo.
*/
FSL_EXPORT 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.
*/
FSL_EXPORT 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().

    FIXME: add a flag specifying whether to drop or keep existing
    copies.

    Returns 0 on success.
*/
FSL_EXPORT int fsl_cx_ticket_create_table(fsl_cx * 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.
*/
FSL_EXPORT 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
*/
FSL_EXPORT int fsl_cx_ticket_load_fields(fsl_cx * 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.
*/
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, when performing a
    checkin, and otherwise as needed, and so calling it from other
    code is normally not necessary.

    @see fsl_ckout_version_write()
*/
FSL_EXPORT int fsl_ckout_version_fetch( fsl_cx *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()
*/
FSL_EXPORT int fsl_ckout_version_write( fsl_cx *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.
*/
FSL_EXPORT int fsl_vfile_to_ckout(fsl_cx * 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.
*/
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_content_buffer_yield(fsl_cx * 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.
*/
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_ckout_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_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.
*/
FSL_EXPORT int fsl_ckout_install_schema(fsl_cx *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.
*/
FSL_EXPORT unsigned int fsl_ckout_rm_empty_dirs(fsl_cx * f, fsl_buffer * 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 reach. 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()
*/
FSL_EXPORT int fsl_ckout_rm_empty_dirs_for_file(fsl_cx * 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.
*/
FSL_EXPORT int fsl_cx_update_seen_delta_mf(fsl_cx *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_EXPORT fsl_buffer * fsl_cx_scratchpad(fsl_cx *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.
*/
FSL_EXPORT void fsl_cx_scratchpad_yield(fsl_cx *f, fsl_buffer * 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 if !d or !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-03) 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()
*/
FSL_EXPORT int fsl_deck_crosslink( fsl_deck /* 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.
*/
FSL_EXPORT int fsl_deck_crosslink_one( fsl_deck * 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.
*/
FSL_EXPORT int fsl_ckout_safe_file_check(fsl_cx *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.
*/
FSL_EXPORT int fsl_ckout_symlink_create(fsl_cx * 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.
*/
FSL_EXPORT int fsl_cx_find_filename_changes(fsl_cx * 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_NOFOUND 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.
*/
FSL_EXPORT int fsl_is_locally_modified(fsl_cx * 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.
*/
FSL_EXPORT void fsl_deck_clean_cards(fsl_deck * d, char const * letters);

/** @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 does not support the "version 0" fossil fingerprint. That
   one was very short-lived and is not expected to be in any/many
   repositories which are accessed via this library.

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

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






































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted include/fossil-scm/fossil-pages.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
/* -*- 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_PAGES_H_INCLUDED)
#define ORG_FOSSIL_SCM_PAGES_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 contains only Doxygen-format documentation, split up into
  Doxygen "pages", each covering some topic at a high level.  This is
  not the place for general code examples - those belong with their
  APIs.
*/

/** @mainpage libfossil

    Forewarning: this API assumes one is familiar with the Fossil SCM,
    ideally in detail. The Fossil SCM can be found at:

    https://fossil-scm.org

    libfossil is an experimental/prototype library API for the Fossil
    SCM. This API concerns itself only with the components of fossil
    which do not need user interaction or the display of UI components
    (including HTML and CLI output). It is intended only to model the
    core internals of fossil, off of which user-level applications
    could be built.

    The project's repository and additional information can be found at:

    https://fossil.wanderinghorse.net/r/libfossil/

    This code is 100% hypothetical/potential, and does not represent
    any Official effort of the Fossil project. It is up for any amount
    of change at any time and does not yet have a stable API.

    All Fossil users are encouraged to participate in its development,
    but if you are reading this then you probably already knew that
    :).

    This effort does not represent "Fossil Version 2", but provides an
    alternate method of accessing and manipulating fossil(1)
    repositories. Whereas fossil(1) is a monolithic binary, this API
    provides library-level access to (some level of) the fossil(1)
    feature set (that level of support grows approximately linearly
    with each new commit).

    Current status: alpha. Some bits are basically finished but there
    is a lot of work left to do. The scope is pretty much all
    Fossil-related functionality which does not require a user
    interface or direct user interaction, plus some range of utilities
    to support those which require a UI/user.
*/

/** @page page_terminology Fossil Terminology

    See also: https://fossil-scm.org/home/doc/trunk/www/concepts.wiki

    The libfossil API docs normally assume one is familiar with
    Fossil-internal terminology, which is of course a silly assumption
    to make. Indeed, one of libfossil's goals is to make Fossil more
    accessible, partly be demystifying it. To that end, here is a
    collection of terms one may come across in the API, along with
    their meanings in the context of Fossil...


    - REPOSITORY (a.k.a. "repo) is an sqlite database file which
    contains all content for a given "source tree." (We will use the
    term "source tree" to mean any tree of "source" (documents,
    whatever) a client has put under Fossil's supervision.)

    - CHECKOUT (a.k.a. "local source tree" or "working copy") refers
    to (A) the action of pulling a specific version of a repository's
    state from that repo into the local filesystem, and (B) a local
    copy "checked out" of a repo. e.g. "he checked out the repo," and
    "the changes are in his [local] checkout."

    - ARTIFACT is the generic term for anything stored in a repo. More
    specifically, ARTIFACT refers to "control structures" Fossil uses
    to internally track changes. These artifacts are stored as blobs
    in the database, just like any other content. For complete details
    and examples, see:
    https://fossil-scm.org/home/doc/tip/www/fileformat.wiki

    - A MANIFEST is a specific type of ARTIFACT - the type which
    records all metadata for a COMMIT operation (which files, which
    user, the timestamp, checkin comment, lineage, etc.). For
    historical reasons, MANIFEST is sometimes used as a generic term
    for ARTIFACT because what the fossil(1)-internal APIs originally
    called a Manifest eventually grew into other types of artifacts
    but kept the Manifest naming convention. In Fossil developer
    discussion, "manifest" most often means what this page calls
    ARTIFACT (probably because that how the C code is modelled).  The
    libfossil API calls uses the term "deck" instead of "manifest" to
    avoid ambiguity/confusion (or to move the confusion somewhere
    else, at least).

    - CHECKIN is the term libfossil prefers to use for COMMIT
    MANIFESTS. It is also the action of "checking in"
    (a.k.a. "committing") file changes to a repository.  A CHECKIN
    ARTIFACT can be one of two types: a BASELINE MANIFEST (or BASELINE
    CHECKIN) contains a list of all files in that version of the
    repository, including their file permissions and the UUIDs of
    their content. A DELTA MANFIEST is a checkin record which derives
    from a BASELINE MANIFEST and it lists only the file-level changes
    which happened between the baseline and the delta, recording any
    changes in content, permisions, or name, and recording
    deletions. Note that this inheritance of deltas from baselines is
    an internal optimization which has nothing to do with checkin
    version inheritance - the baseline of any given delta is normally
    _not_ its direct checkin version parent.

    - BRANCH, FORK, and TAG are all closely related in Fossil and are
    explained in detail (with pictures!) at:
    https://fossil-scm.org/home/doc/trunk/www/concepts.wiki
    In short: BRANCHes and FORKs are two names for the same thing, and
    both are just a special-case usage of TAGs.

    - MERGE or MERGING: the process of integrating one version of
    source code into another version of that source code, using a
    common parent version as the basis for comparison. This is
    normally fully automated, but occasionally human (and sometimes
    Divine) intervention is required to resolve so-called "merge
    conflicts," where two versions of a file change the same parts of
    a common parent version.

    - RID (Record ID) is a reference to the blob.rid field in a
    repository DB. RIDs are used extensively throughout the API for
    referencing content records, but they are transient values local
    to a given copy of a given repository at a given point in
    time. They _can_ change, even for the same content, (e.g. a
    rebuild can hypothetically change them, though it might not, and
    re-cloning a repo may very well change some RIDs). Clients must
    never rely on them for long-term reference to SCM'd data - always use
    the full UUID of such data. Even though they normally appear to be
    static, they are most explicitly NOT guaranteed to be. Nor are
    their values guaranteed to imply any meaning, e.g. "higher is
    newer" is not necessarily true because synchronization can import
    new remote content in an arbitrary order and a rebuild might
    import it in random order. The API uses RIDs basically as handles
    to arbitrary blob content and, like most C-side handles, must be
    considered transient in nature. That said, within the db, records
    are linked to each other exclusively using RIDs, so they do have
    some persistence guarantees for a given db instance.
*/


/** @page page_APIs High-level API Overview

    The primary end goals of this project are to eventually cover the
    following feature areas:

    - Provide embeddable SCM to local apps using sqlite storage.
    - Provide a network layer on top of that for synchronization.
    - Provide apps on top of those to allow administration of repos.

    To those ends, the fossil APIs cover the following categories of
    features:

    Filesystem:

    - Conversions of strings from OS-native encodings to UTF.
    fsl_utf8_to_unicode(), fsl_filename_to_utf8(), etc. These are
    primarily used internally but may also be useful for applications
    working with files (as most clients will). Actually... most of
    these bits are only needed for portability across Windows
    platforms.

    - Locating a user's home directory: fsl_find_home_dir()

    - Normalizing filenames/paths. fsl_file_canonical_name() and friends.

    - Checking for existence, size, and type (file vs directory) with
    fsl_is_file() and fsl_dir_check(), or the more general-purpose
    fsl_stat().


    Databases (sqlite):

    - Opening/closing sqlite databases and running queries on them,
    independent of version control features. See fsl_db_open() and
    friends. The actual sqlite-level DB handle type is abstracted out
    of the public API, largely to simplify an eventual port from
    sqlite3 to sqlite4 or (hypothetically) to other storage back-ends
    (not gonna happen - too much work).

    - There are lots of utility functions for oft-used operations,
    e.g. fsl_config_get_int32() and friends to fetch settings from one
    of several different configuration areas (global, repository,
    checkout, and "versionable" settings).

    - Pseudo-recusive transactions: fsl_db_transaction_begin() and
    fsl_db_transaction_end(). sqlite does not support truly nested
    transactions, but they can be simulated quite effectively so long
    as certain conventions are adhered to.

    - Cached statements (an optimization for oft-used queries):
    fsl_db_prepare_cached() and friends.


    The DB API is (as Brad Harder put so well) "very present" in the
    public API. While the core API provides access to the underlying
    repository data, it cannot begin to cover even a small portion of
    potential use cases. To that end, it exposes the DB API so that
    clients who want to custruct their own data can do so. It does
    require research into the underlying schemas, but gives
    applications the ability to do _anything_ with their repositories
    which the core API does not account for. Historically, the ability
    to create ad-hoc data structures as needed, in the form of SQL
    queries, has accounted for much of Fossil's feature flexibility.


    Deltas:

    - Creation and application of raw deltas, using Fossil's delta
    format, independent of version control features. See
    fsl_delta_create() and friends. These are normally used only at
    the deepest internal levels of fossil, but the APIs are exposed so
    that clients can, if they wish, use them to deltify their own
    content independently of fossil's internally-applied
    deltification. Doing so is remarkably easy, but completely
    unnecessary for content which will be stored in a repo, as Fossil
    creates deltas as needed.


    SCM:

    - A "context" type (fsl_cx) which manages a repository db and,
    optionally, a checkout db. Read-only operations on the DB are
    working and write functionality (adding repo content) is
    ongoing. See fsl_cx, fsl_cx_init(), and friends.

    - The fsl_deck class assists in parsing, creating, and outputing
    "artifacts" (manifests, control (tags), events, etc.). It gets its
    name from it being container for "a collection of cards" (which is
    what a Fossil artifact is).

    - fsl_content_get() expands a (possibly) deltified blob into its
    full form, and fsl_content_blob() can be used to fetch a raw blob
    (possibly a raw delta).

    - A number of routines exist for converting symbol names to RIDs
    (fsl_sym_to_rid()), UUIDs to RIDs (fsl_uuid_to_rid(),
    and similar commonly-needed lookups.


    Input/Output:

    - The API defines several abstractions for i/o interfaces, e.g.
    fsl_input_f() and fsl_output_f(), which allow us to accept/emit
    data from/to arbitrary streamable (as opposed to random-access)
    sources/destinations. A fsl_cx instance is configured with an
    output channel, the intention being that all clients of that
    context should generate any output through that channel, so that
    all compatible apps can cooperate more easily in terms of i/o. For
    example, the s2 script binding for libfossil routes fsl_output()
    through the script engine's i/o channels, so that any output
    generated by libfossil-using code it links to can take advantage
    of the script-side output features (such as output buffering,
    which is needed for any non-trivial CGI output). That said: the
    library-level code does not actually generate output to that
    channel, but higher-level code like fcli does, and clients are
    encouraged to in order to enable their app's output to be
    redirected to an arbitrary UI element, be it a console or UI
    widget.


    Utilities:

    - fsl_buffer, a generic buffer class, is used heavily by the
    library.  See fsl_buffer and friends.

    - fsl_appendf() provides printf()-like functionality, but sends
    its output to a callback function (optionally stateful), making it
    the one-stop-shop for string formatting within the library.

    - The fsl_error class is used to propagate error information
    between the libraries various levels and the client.

    - The fsl_list class acts as a generic container-of-pointers, and
    the API provides several convenience routines for managing them,
    traversing them, and cleaning them up.

    - Hashing: there are a number of routines for calculating SHA1,
    SHA3, and MD5 hashes. See fsl_sha1_cx, fsl_sha3_cx, fsl_md5_cx,
    and friends.

    - zlib compression is used for storing artifacts. See
    fsl_data_is_compressed(), fsl_buffer_compress(), and friends.
    These are never needed at the client level, but are exposed "just
    in case" a given client should want them.
*/

/** @page page_is_isnot Fossil is/is not...

    Through porting the main fossil application into library form,
    the following things have become very clear (or been reinforced)...

    Fossil is...

    - _Exceedingly_ robust. Not only is sqlite literally the single
    most robust application-agnostic container file format on the
    planet, but Fossil goes way out of its way to ensure that what
    gets put in is what gets pulled out. It cuts zero corners on data
    integrity, even adding in checks which seem superfluous but
    provide another layer of data integrity (i'm primarily talking
    about the R-card here, but there are other validation checks). It
    does this at the cost of memory and performance (that said, it's
    still easily fast enough for its intended uses). "Robust" doesn't
    mean that it never crashes nor fails, but that it does so with
    (insofar as is technically possible) essentially zero chance of
    data loss/corruption.

    - Long-lived: the underlying data format is independent of its
    storage format. It is, in principal, usable by systems as yet
    unconceived by the next generation of programmers. This
    implementation is based on sqlite, but the model can work with
    arbitrary underlying storage.

    - Amazingly space-efficient. The size of a repository database
    necessarily grows as content is modified. However, Fossil's use of
    zlib-compressed deltas, using a very space-efficient delta format,
    leads to tremendous compression ratios. As of this writing (March,
    2021), the main Fossil repo contains approximately 5.36GB of
    content, were we to check out every single version in its
    history. Its repository database is only 64MB, however, equating
    to a 83:1 compression ration. Ratios in the range of 20:1 to 40:1
    are common, and more active repositories tend to have higher
    ratios. The TCL core repository, with just over 15 years of code
    history (imported, of course, as Fossil was introduced in 2007),
    is (as of September 2013) only 187MB, with 6.2GB of content and a
    33:1 compression ratio.

    Fossil is not...

    - Memory-light. Even very small uses can easily suck up 1MB of RAM
    and many operations (verification of the R card, for example) can
    quickly allocate and free up hundreds of MB because they have to
    compose various versions of content on their way to a specific
    version. To be clear, that is total RAM usage, not _peak_ RAM
    usage. Peak usage is normally a function of the content it works
    with at a given time, often in direct relation to (but
    significantly more than) the largest single file processed in a
    given session. For any given delta application operation, Fossil
    needs the original content, the new content, and the delta all in
    memory at once, and may go through several such iterations while
    resolving deltified content. Verification of its 'R-card' alone
    can require a thousand or more underlying DB operations and
    hundreds of delta applications. The internals use caching where it
    would save us a significant amount of db work relative to the
    operation in question, but relatively high memory costs are
    unavoidable. That's not to say we can't optimize a bit, but first
    make it work, then optimize it. The library takes care to re-use
    memory buffers where it is feasible (and not too intrusive) to do
    so, but there is yet more RAM to be optimized away in this regard.
*/

/** @page page_threading Threads and Fossil

    It is strictly illegal to use a given fsl_cx instance from more
    than one thread. Period.

    It is legal for multiple contexts to be running in multiple
    threads, but only if those contexts use different
    repository/checkout databases. Though access to the storage is,
    through sqlite, protected via a mutex/lock, this library does not
    have a higher-level mutex to protect multiple contexts from
    colliding during operations. So... don't do that. One context, one
    repo/checkout.

    Multiple application instances may each use one fsl_cx instance to
    share repo/checkout db files, but must be prepared to handle
    locking-related errors in such cases. e.g. db operations which
    normally "always work" may suddenly pause for a few seconds before
    giving up while waiting on a lock when multiple applications use
    the same database files. sqlite's locking behaviours are
    documented in great detail at https://sqlite.org.
 */

/** @page page_artifacts Creating Artifacts

    A brief overview of artifact creating using this API. This is targeted
    at those who are familiar with how artifacts are modelled and generated
    in fossil(1).

    Primary artifact reference:

    https://fossil-scm.org/home/doc/trunk/www/fileformat.wiki

    In fossil(1), artifacts are generated via the careful crafting of
    a memory buffer (large string) in the format described in the
    document above. While it's relatively straightforward to do, there
    are lots of potential gotchas, and a bug can potentially inject
    "bad data" into the repo (though the verify-before-commit process
    will likely catch any problems before the commit is allowed to go
    through). The libfossil API uses a higher-level (OO) approach,
    where the user describes a "deck" of cards and then tells the
    library to save it in the repo (fsl_deck_save()) or output it to
    some other channel (fsl_deck_output()). The API ensures that the
    deck's cards get output in the proper order and that any cards
    which require special treatment get that treatment (e.g. the
    "fossilize" encoding of certain text fields). The "deck" concept
    is equivalent to Artifact in fossil(1), but we use the word deck
    because (A) Artifact is highly ambiguous in this context and (B)
    deck is arguably the most obvious choice for the name of a type
    which acts as a "container of cards."

    Ideally, client-level code will never have to create an artifact
    via the fsl_deck API (because doing so requires a fairly good
    understanding of what the deck is for in the first place,
    including the individual Cards). The public API strives to hide
    those levels of details, where feasible, or at least provide
    simpler/safer alternatives for basic operations. Some operations
    may require some level of direct work with a fsl_deck
    instance. Likewise, much read-only functionality directly exposes
    fsl_deck to clients, so some familiarity with the type and its
    APIs will be necessary for most clients.

    The process of creating an artifact looks a lot like the following
    code example. We have elided error checking for readability
    purposes, but in fact this code has undefined behaviour if error
    codes are not checked and appropriately reacted to.

    @code
    fsl_deck deck = fsl_deck_empty;
    fsl_deck * d = &deck; // for typing convenience
    fsl_deck_init( fslCtx, d, FSL_SATYPE_CONTROL ); // must come first
    fsl_deck_D_set( d, fsl_julian_now() );
    fsl_deck_U_set( d, "your-fossil-name", -1 );
    fsl_deck_T_add( d, FSL_TAGTYPE_ADD, "...uuid being tagged...",
                   "tag-name", "optional tag value");
    ...
    // Now output it to stdout:
    fsl_deck_output( f, d, fsl_output_f_FILE, stdout );
    // See also: fsl_deck_save(), which stores it in the db and
    // "crosslinks" it.
    fsl_deck_finalize(d);
    @endcode

    The order the cards are added to the deck is irrelevant - they
    will be output in the order specified by the Fossil specs
    regardless of their insertion order. Each setter/adder function
    knows, based on the deck's type (set via fsl_deck_init()), whether
    the given card type is legal, and will return an error (probably
    FSL_RC_TYPE) if an attempt is made to add a card which is illegal
    for that deck type. Likewise, fsl_deck_output() and
    fsl_deck_save() confirm that the decks they are given contain (A)
    only allowed cards and (B) have all required
    cards. fsl_deck_output() will "unshuffle" the cards, making sure
    they're in the correct order.

    Sidebar: normally outputing a structure can use a const form of
    that structure, but the traversal of F-cards in a deck requires
    (for the sake of delta manifests) using a non-const cursor. Thus
    outputing a deck requires a non-const instance. If it weren't for
    delta manifests, we could be "const-correct" here.
*/

/** @page page_transactions DB Transactions

    The fsl_db_transaction_begin() and fsl_db_transaction_end()
    functions implement a basic form of recursive transaction,
    allowing the library to start and end transactions at any level
    without having to know whether a transaction is already in
    progress (sqlite3 does not natively support nested
    transactions). A rollback triggered in a lower-level transaction
    will propagate the error back through the transaction stack and
    roll back the whole transaction, providing us with excellent error
    recovery capabilities (meaning we can always leave the db in a
    well-defined state).

    It is STRICTLY ILLEGAL to EVER begin a transaction using "BEGIN"
    or end a transaction by executing "COMMIT" or "ROLLBACK" directly
    on a fsl_db instance. Doing so bypasses internal state which needs
    to be kept abreast of things and will cause Grief and Suffering
    (on the client's part, not mine).

    Tip: implementing a "dry-run" mode for most fossil operations is
    trivial by starting a transaction before performing the
    operations. Many operations run in a transaction, but if the
    client starts one of his own they can "dry-run" any op by simply
    rolling back the transaction he started. Abstractly, that looks
    like this pseudocode:

    @code
    db.begin();
    fsl.something();
    fsl.somethingElse();
    if( dryRun ) db.rollback();
    else db.commit();
    @endcode
*/

/** @page page_code_conventions Code Conventions

    Project and Code Conventions...

    Foreward: all of this more or less evolved organically or was
    inherited from fossil(1) (where it evolved organically, or was
    inherited from sqilte (where it evol...)), and is written up here
    more or less as a formality. Historically i've not been a fan of
    coding conventions, but as someone else put it to me, "the code
    should look like it comes from a single source," and the purpose
    of this section is to help orient those looking to hack in the
    sources. Note that most of what is said below becomes obvious
    within a few minutes of looking at the sources - there's nothing
    earth-shatteringly new nor terribly controversial here.

    The Rules/Suggestions/Guidelines/etc. are as follows...


    - C99 is the basis. It was C89 until 2021-02-12.

    - The canonical build environment uses the most restrictive set of
    warning/error levels possible. It is highly recommended that
    non-canonical build environments do the same. Adding -Wall -Werror
    -pedantic does _not_ guaranty that all C compliance/portability
    problems can be caught by the compiler, but it goes a long way in
    helping us to write clean code. The clang compiler is particularly
    good at catching subtle foo-foo's such as uninitialized variables.

    - API docs (as you may have already noticed), does not (any
    longer) follow Fossil's comment style, but instead uses
    Doxygen-friendly formatting. Each comment block MUST start with
    two or more asterisks, or '*!', or doxygen apparently doesn't
    understand it
    (https://www.stack.nl/~dimitri/doxygen/manual/docblocks.html). When
    adding code snippets and whatnot to docs, please use doxygen
    conventions if it is not too much of an inconvenience. All public
    APIs must be documented with a useful amount of detail. If you
    hate documenting, let me know and i'll document it (it's what i do
    for fun).

    - Public API members have a fsl_ or FSL_ prefix (fossil_ seems too
    long). For private/static members, anything goes. Optional or
    "add-on" APIs (e.g. ::fcli) may use other prefixes, but are
    encouraged use an "f-word" (as it were), simply out of deference
    to long-standing software naming conventions.

    - Public-API structs and functions use lower_underscore_style().
    Static/internal APIs may use different styles. It's not uncommon
    to see UpperCamelCase for file-scope structs.

    - Overall style, especially scope blocks and indentation, should
    follow Fossil's.  We are _not at all_ picky about whether or not
    there is a space after/before parens in if( foo ), and similar
    small details, just the overall code pattern.

    - Structs and enums all get the optional typedef so that they do
    not need to be qualified with 'struct' resp. 'enum' when
    used. Because of how doxygen tracks those, the typedef should be
    separate from the struct declaration, rather than combinding
    those.

    - Function typedefs are named fsl_XXX_f. Implementations of such
    typedefs/interfaces are typically named fsl_XXX_f_SUFFIX(), where
    SUFFIX describes the implementation's
    specialization. e.g. fsl_output_f() is a callback
    typedef/interface and fsl_output_f_FILE() is a concrete
    implementation for FILE handles.

    - Enums tend to be named fsl_XXX_e.

    - Functions follow the naming pattern prefix_NOUN_VERB(), rather
    than the more C-conventional prefix_VERB_NOUN(),
    e.g. fsl_foo_get() and fsl_foo_set() rather than fsl_get_foo() and
    fsl_get_foo(). The primary reasons are (A) sortability for
    document processors and (B) they more naturally match with OO API
    conventions, e.g.  noun.verb(). A few cases knowingly violate this
    convention for the sake of readability or sorting of several
    related functions (e.g. fsl_db_get_TYPE() instead of
    fsl_db_TYPE_get()).

    - Structs intended to be creatable on the stack are accompanied by
    a const instance named fsl_STRUCT_NAME_empty, and possibly by a
    macro named fsl_STRUCT_NAME_empty_m, both of which are
    "default-initialized" instances of that struct. This is superiour
    to using memset() for struct initialization because we can define
    (and document) arbitrary default values and all clients who
    copy-construct them are unaffected by many types of changes to the
    struct's signature (though they may need a recompile). The
    intention of the fsl_STRUCT_NAME_empty_m macro is to provide a
    struct-embeddable form for use in other structs or
    copy-initialization of const structs, and the _m macro is always
    used to initialize its const struct counterpart. e.g. the library
    guarantees that fsl_cx_empty_m (a macro representing an empty
    fsl_cx instance) holds the same default values as fsl_cx_empty (a
    const fsl_cx value).

    - Returning int vs fsl_int_t vs fsl_size_t: int is used as a
    conventional result code. fsl_int_t is often used as a signed
    length-style result code (e.g. printf() semantics). Unsigned
    ranges use fsl_size_t. Ints are (also) used as a "triplean" (3
    potential values, e.g. <0, 0, >0). fsl_int_t also guarantees that
    it will be 64-bit if available, so can be used for places where
    large values are needed but a negative value is legal (or handy),
    e.g. fsl_strndup()'s second argument. The use of the fsl_xxx_t
    typedefs, rather than (unsigned) int, is primarily for
    readability/documentation, e.g. so that readers can know
    immediately that the function uses a given argument or return
    value following certain API-wide semantics. It also allows us to
    better define platform-portable printf/scanf-style format
    modifiers for them (analog to C99's PRIi32 and friends), which
    often come in handy.

    - Signed vs. unsigned types for size/length arguments: use the
    fsl_int_t (signed) argument type when the client may legally pass
    in a negative value as a hint that the API should use fsl_strlen()
    (or similar) to determine a byte array's length. Use fsl_size_t
    when no automatic length determination is possible (or desired),
    to "force" the client to pass the proper length. Internally
    fsl_int_t is used in some places where fsl_size_t "should" be used
    because some ported-in logic relies on loop control vars being
    able to go negative. Additionally, fossil internally uses negative
    blob lengths to mark phantom blobs, and care must be taken when
    using fsl_size_t with those.

    - Functions taking elipses (...) are accompanied by a va_list
    counterpart named the same as the (...) form plus a trailing
    'v'. e.g. fsl_appendf() and fsl_appendfv(). We do not use the
    printf()/vprintf() convention because that hoses sorting of the
    functions in generated/filtered API documentation.

    - Error handling/reporting: please keep in mind that the core code
    is a library, not an application.  The main implication is that
    all lib-level code needs to check for errors whereever they can
    happen (e.g. on every single memory allocation, of which there are
    many) and propagate errors to the caller, to be handled at his
    discretion. The app-level code (::fcli) is not particularly strict
    in this regard, and installs its own allocator which abort()s on
    allocation error, which simplifies app-side code somewhat
    vis-a-vis lib-level code. When reporting an error can be improved
    by the inclusion of an error string, functions like
    fsl_cx_err_set() can be used to report the error. Several of the
    high-level types in the API have fsl_error object member which
    contains such error state. The APIs which use that state take care
    to use-use the error string memory whenever possible, so setting
    an error string is often a non-allocating operation.
*/


/** @page page_fossil_arch Fossil Architecture Overview

    An introduction to the Fossil architecture. These docs
    are basically just a reformulation of other, more detailed,
    docs which can be found via the main Fossil site, e.g.:

    - https://fossil-scm.org/home/doc/trunk/www/concepts.wiki

    - https://fossil-scm.org/home/doc/trunk/www/fileformat.wiki

    Fossil's internals are fundamentally broken down into two basic
    parts. The first is a "collection of blobs."  The simplest way to
    think of this (and it's not far from the full truth) is a
    directory containing lots of files, each one named after a hash of
    its contents. This pool contains ALL content required for a
    repository - all other data can be generated from data contained
    here. Included in the blob pool are so-called Artifacts. Artifacts
    are simple text files with a very strict format, which hold
    information regarding the idententies of, relationships involving,
    and other metadata for each type of blob in the pool. The most
    fundamental Artifact type is called a Manifest, and a Manifest
    tells us, amongst other things, which of the hash-based file names
    has which "real" file name, which version the parent (or parents!)
    is (or are), and other data required for a "commit" operation.

    The blob pool and the Manifests are all a Fossil repository really
    needs in order to function. On top of that basis, other forms of
    Artifacts provide features such as tagging (which is the basis of
    branching and merging), wiki pages, and tickets. From those
    Artifacts, Fossil can create/calculate all sorts of
    information. For example, as new Artifacts are inserted it
    transforms the Artifact's metadata into a relational model which
    sqlite can work with. That leads us to what is conceptually the
    next-higher-up level, but is in practice a core-most component...

    Storage. Fossil's core model is agnostic about how its blobs are
    stored, but libfossil and fossil(1) both make heavy use of sqlite
    to implement many of their features. These include:

    - Transaction-capable storage. It's almost impossible to corrupt a
    Fossil db in normal use. sqlite3 offers literally the most robust
    general-purpose file format on the planet.

    - The storage of the raw blobs.

    - Artifact metadata is transformed into various DB structures
    which allow libfossil to traverse historical data much more
    efficiently than would be possible without a db-like
    infrastructure (and everything that implies). These structures are
    kept up to date as new Artifacts are stored in a repository,
    either via local edits or synching in remote content. These data
    are incrementally updated as changes are made to a repo.

    - A tremendous amount of the "leg-work" in processing the
    repository state is handled by SQL queries, without which the
    library would easily require 5-10x more code in the form of
    equivalent hard-coded data structures and corresponding
    functionality. The db approach allows us to ad-hoc structures as
    we need them, providing us a great deal of flexibility.

    All content in a Fossil repository is in fact stored in a single
    database file. Fossil additionally uses another database (a
    "checkout" db) to keep track of local changes, but the repo
    contains all "fossilized" content. Each copy of a repo is a
    full-fledged repo, each capable of acting as a central copy for
    any number of clones or checkouts.

    That's really all there is to understand about Fossil. How it does
    its magic, keeping everything aligned properly, merging in
    content, how it stores content, etc., is all internal details
    which most clients will not need to know anything about in order
    to make use of fossil(1). Using libfossil effectively, though,
    does require learning _some_ amount of how Fossil works. That will
    require taking some time with _other_ docs, however: see the
    links at the top of this section for some starting points.


    Sidebar:

    - The only file-level permission Fossil tracks is the "executable"
    (a.k.a. "+x") bit. It internally marks symlinks as a permission
    attribute, but that is applied much differently than the
    executable bit and only does anything useful on platforms which
    support symlinks.

*/

#endif
/* ORG_FOSSIL_SCM_PAGES_H_INCLUDED */
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted include/fossil-scm/fossil-repo.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
/* -*- 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_REPO_H_INCLUDED)
#define ORG_FOSSIL_SCM_FSL_REPO_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).
*/

/** @file fossil-repo.h

    fossil-repo.h declares APIs specifically dealing with
    repository-db-side state, as opposed to specifically checkout-side
    state or non-content-related APIs.
*/

#include "fossil-db.h" /* MUST come first b/c of config macros */
#include "fossil-hash.h"

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

typedef struct fsl_card_F fsl_card_F;
typedef struct fsl_card_J fsl_card_J;
typedef struct fsl_card_Q fsl_card_Q;
typedef struct fsl_card_T fsl_card_T;
typedef struct fsl_checkin_opt fsl_checkin_opt;
typedef struct fsl_deck fsl_deck;

/**
   This function is a programmatic interpretation of
   this table:

   https://fossil-scm.org/index.html/doc/trunk/www/fileformat.wiki#summary

   For a given control artifact type and a card name in the form of
   the card name's letter (e.g. 'A', 'W', ...), this function
   returns 0 (false) if that card type is not permitted in this
   control artifact type, a negative value if the card is optional
   for this artifact type, and a positive value if the card type is
   required for this artifact type.

   As a special case, if t==FSL_SATYPE_ANY then this function
   always returns a negative value as long as card is a valid card
   letter.

   Another special case: when t==FSL_SATYPE_CHECKIN and card=='F',
   this returns a negative value because the table linked to above
   says F-cards are optional. In practice we have yet to find a use
   for checkins with no F-cards, so this library currently requires
   F-cards at checkin-time even though this function reports that
   they are optional.
*/
FSL_EXPORT int fsl_card_is_legal( fsl_satype_e t, char card );

/**
   Artifact tag types used by the Fossil framework. Their values
   are a hard-coded part of the Fossil format, and not subject to
   change (only extension, possibly).
*/
enum fsl_tagtype_e {
/**
   Sentinel value for use with constructors/initializers.
*/
FSL_TAGTYPE_INVALID = -1,
/**
   The "cancel tag" indicator, a.k.a. an anti-tag.
*/
FSL_TAGTYPE_CANCEL = 0,
/**
   The "add tag" indicator, a.k.a. a singleton tag.
*/
FSL_TAGTYPE_ADD = 1,
/**
   The "propagating tag" indicator.
*/
FSL_TAGTYPE_PROPAGATING = 2
};
typedef enum fsl_tagtype_e fsl_tagtype_e;

/**
   Hard-coded IDs used by the 'tag' table of repository DBs. These
   values get installed as part of the base Fossil db schema in new
   repos, and they must never change.
*/
enum fsl_tagid_e {
/**
   DB string tagname='bgcolor'.
*/
FSL_TAGID_BGCOLOR = 1,
/**
   DB: tag.tagname='comment'.
*/
FSL_TAGID_COMMENT = 2,
/**
   DB: tag.tagname='user'.
*/
FSL_TAGID_USER = 3,
/**
   DB: tag.tagname='date'.
*/
FSL_TAGID_DATE = 4,
/**
   DB: tag.tagname='hidden'.
*/
FSL_TAGID_HIDDEN = 5,
/**
   DB: tag.tagname='private'.
*/
FSL_TAGID_PRIVATE = 6,
/**
   DB: tag.tagname='cluster'.
*/
FSL_TAGID_CLUSTER = 7,
/**
   DB: tag.tagname='branch'.
*/
FSL_TAGID_BRANCH = 8,
/**
   DB: tag.tagname='closed'.
*/
FSL_TAGID_CLOSED = 9,
/**
   DB: tag.tagname='parent'.
*/
FSL_TAGID_PARENT = 10,
/**
   DB: tag.tagname='note'

   Extra text appended to a check-in comment.
*/
FSL_TAGID_NOTE = 11,

/**
   Largest tag ID reserved for internal use.
*/
FSL_TAGID_MAX_INTERNAL = 99
};


/**
   Returns one of '-', '+', or '*' for a valid input parameter, 0
   for any other value.
*/
FSL_EXPORT char fsl_tag_prefix_char( fsl_tagtype_e t );


/**
   A list of fsl_card_F objects. F-cards used a custom list type,
   instead of the framework's generic fsl_list, because experience has
   shown that the number of (de)allocations we need for F-card lists
   has a large negative impact when parsing and creating artifacts en
   masse. This list type, unlike fsl_list, uses a conventional object
   array approach to storage, as opposed to an array of pointers (each
   entry of which has to be separately allocated).

   These lists, and F-cards in generally, are typically maintained
   internally in the library. There's probably "no good reason" for
   clients to manipulate them.
*/
struct fsl_card_F_list {
  /**
     The list of F-cards. The first this->used elements are in-use.
     This pointer may change any time the list is reallocated.a
  */
  fsl_card_F * list;
  /**
     The number of entries in this->list which are in use.
  */
  uint32_t used;
  /**
     The number of entries currently allocated in this->list.
  */
  uint32_t capacity;
  /**
     An internal cursor into this->list, used primarily for
     properly traversing the file list in delta manifests.

     Maintenance notes: internal updates to this member are the only
     reason some of the deck APIs require a non-const deck. This type
     needs to be signed for compatibility with some of the older
     algos, e.g. fsl_deck_F_seek_base().
  */
  int32_t cursor;
  /**
     Internal flags. Never, ever modify these from client code.
  */
  uint32_t flags;
};
typedef struct fsl_card_F_list fsl_card_F_list;
/** Empty-initialized fsl_card_F instance for const copy
    initialization */
#define fsl_card_F_list_empty_m {NULL, 0, 0, 0, 0}
/** Empty-initialized fsl_card_F instance for non-const copy
    initialization */
FSL_EXPORT const fsl_card_F_list fsl_card_F_list_empty;

/**
   A "deck" stores (predictably enough) a collection of "cards."
   Cards are constructs embedded within Fossil's Structural Artifacts
   to denote various sorts of changes in a Fossil repository, and a
   Deck encapsulates the cards for a single Structural Artifact of an
   arbitrary type, e.g. Manifest (a.k.a. "checkin") or Cluster. A card
   is basically a command with a single-letter name and a well-defined
   signature for its arguments. Each card is represented by a member
   of this struct whose name is the same as the card type
   (e.g. fsl_card::C holds a C-card and fsl_card::F holds a list of
   F-card). Each type of artifact only allows certain types of
   card. The complete list of valid card/construct combinations can be
   found here:

   https://fossil-scm.org/home/doc/trunk/www/fileformat.wiki#summary

   fsl_card_is_legal() can be used determine if a given card type
   is legal (per the above chart) with a given Control Artifiact
   type (as stored in the fsl_deck::type member).

   The type member is used by some algorithms to determine which
   operations are legal on a given artifact type, so that they can
   fail long before the user gets a chance to add a malformed artifact
   to the database. Clients who bypass the fsl_deck APIs and
   manipulate the deck's members "by hand" (so to say) effectively
   invoke undefined behaviour.

   The various routines to add/set cards in the deck are named
   fsl_deck_CARDNAME_add() resp. fsl_deck_CARDNAME_set(). The "add"
   functions represent cards which may appear multiple times
   (e.g. the 'F' card) or have multiple values (the 'P' card), and
   those named "set" represent unique or optional cards. The R-card
   is the outlier, with fsl_deck_R_calc(). NEVER EVER EVER directly
   modify a member of this struct - always use the APIs. The
   library performs some optimizations which can lead to corrupt
   memory and invalid free()s if certain members' values are
   directly replaced by the client (as opposed to via the APIs).

   Note that the 'Z' card is not in this structure because it is a
   hash of the other inputs and is calculated incrementally and
   appended automatically by fsl_deck_output().

   All non-const pointer members of this structure are owned by the
   structure instance unless noted otherwise (the fsl_deck::f member
   being the notable exception).

   Maintenance reminder: please keep the card members alpha sorted to
   simplify eyeball-searching through their docs.

   @see fsl_deck_malloc()
   @see fsl_deck_init()
   @see fsl_deck_parse()
   @see fsl_deck_load_rid()
   @see fsl_deck_finalize()
   @see fsl_deck_clean()
   @see fsl_deck_save()
   @see fsl_deck_A_set()
   @see fsl_deck_B_set()
   @see fsl_deck_D_set()
   @see fsl_deck_E_set()
   @see fsl_deck_F_add()
   @see fsl_deck_J_add()
   @see fsl_deck_K_set()
   @see fsl_deck_L_set()
   @see fsl_deck_M_add()
   @see fsl_deck_N_set()
   @see fsl_deck_P_add()
   @see fsl_deck_Q_add()
   @see fsl_deck_R_set()
   @see fsl_deck_T_add()
   @see fsl_deck_branch_set()
   @see fsl_deck_U_set()
   @see fsl_deck_W_set()
*/
struct fsl_deck {
  /**
     Specifies the the type (or eventual type) of this
     artifact. The function fsl_card_is_legal() can be used to
     determined if a given card type is legal for a given value of
     this member. APIs which add/set cards use that to determine if
     the operation requested by the client is semantically legal.
  */
  fsl_satype_e type;

  /**
     DB repo.blob.rid value. Normally set by fsl_deck_parse().
  */
  fsl_id_t rid;

  /**
     Gets set by fsl_deck_parse() to the hash/UUID of the
     manifest it parsed.
  */
  fsl_uuid_str uuid;

  /**
     The Fossil context responsible for this deck. Though this data
     type is normally, at least conceptually, free of any given fossil
     context, many related algorithms need a context in order to
     perform db- or caching-related work, as well as to simplify error
     message propagation. We store this as a struct member to keep all
     such algorithms from redundantly requiring both pieces of
     information as arguments.

     This object does not own the context and the context object must
     outlive this deck instance.
  */
  fsl_cx * f;

  /**
     The 'A' (attachment) card. Only used by FSL_SATYPE_ATTACHMENT
     decks. The spec currently specifies only 1 A-card per
     manifest, but conceptually this could/should be a list.
  */
  struct {
    /**
       Filename of the A-card.
    */
    char * name;

    /**
       Name of wiki page, or UUID of ticket or event (technote), to
       which the attachment applies.
    */
    char * tgt;

    /**
       UUID of the file being attached via the A-card.
    */
    fsl_uuid_str src;
  } A;

  struct {
    /**
       The 'B' (baseline) card holds the UUID of baseline manifest.
       This is empty for baseline manifests and holds the UUID of
       the parent for delta manifests.
    */
    fsl_uuid_str uuid;

    /**
       Baseline manifest corresponding to this->B. It is loaded on
       demand by routines which need it, typically by calling
       fsl_deck_F_rewind() (unintuitively enough!). The
       parent/child relationship in Fossil is the reverse of
       conventional - children own their parents, not the other way
       around. i.e. this->baseline will get cleaned up
       (recursively) when this instance is cleaned up (when the
       containing deck is cleaned up).
    */
    fsl_deck * baseline;
  } B;
  /**
     The 'C' (comment) card.
  */
  char * C;

  /**
     The 'D' (date) card, in Julian format.
  */
  double D;

  /**
     The 'E' (event) card.
  */
  struct {
    /**
       The 'E' card's date in Julian Day format.
    */
    double julian;

    /**
       The 'E' card's UUID.
    */
    fsl_uuid_str uuid;
  } E;

  /**
     The 'F' (file) card container.
  */
  fsl_card_F_list F;
  
  /**
     UUID for the 'G' (forum thread-root) card.
  */
  fsl_uuid_str G;

  /**
     The H (forum title) card.
  */
  char * H;

  /**
     UUID for the 'I' (forum in-response-to) card.
  */
  fsl_uuid_str I;

  /**
     The 'J' card specifies changes to "value" of "fields" in
     tickets (FSL_SATYPE_TICKET).

     Holds (fsl_card_J*) entries.
  */
  fsl_list J;

  /**
     UUID for the 'K' (ticket) card.
  */
  fsl_uuid_str K;

  /**
     The 'L' (wiki name/title) card.
  */
  char * L;

  /**
     List of UUIDs (fsl_uuid_str) in a cluster ('M' cards).
  */
  fsl_list M;

  /**
     The 'N' (comment mime type) card. Note that this is only
     ostensibly supported by fossil, but fossil does not (as of
     2021-04-13) honor this value and always assumes that its value is
     "text/x-fossil-wiki".
  */
  char * N;

  /**
     List of UUIDs of parents ('P' cards). Entries are of type
     (fsl_uuid_str).
  */
  fsl_list P;

  /**
     'Q' (cherry pick) cards. Holds (fsl_card_Q*) entries.
  */
  fsl_list Q;

  /**
     The R-card holds an MD5 hash which is calculated based on the
     names, sizes, and contents of the files included in a
     manifest. See the class-level docs for a link to a page which
     describes how this is calculated.
  */
  char * R;

  /**
     List of 'T' (tag) cards. Holds (fsl_card_T*) instances.
  */
  fsl_list T;

  /**
     The U (user) card.
  */
  char * U;

  /**
     The W (wiki content) card.
  */
  fsl_buffer W;

  /**
     This is part of an optimization used when parsing fsl_deck
     instances from source text. For most types of card we re-use
     string values in the raw source text rather than duplicate them,
     and that requires storing the original text (as passed to
     fsl_deck_parse()). This requires that clients never tinker
     directly with values in a fsl_deck, in particular never assign
     over them or assume they know who allocated the memory for that
     bit.
  */
  fsl_buffer content;

  /**
     To potentially be used for a manifest cache.
  */
  fsl_deck * next;

  /**
     A marker which tells fsl_deck_finalize() whether or not
     fsl_deck_malloc() allocated this instance (in which case
     fsl_deck_finalize() will fsl_free() it) or not (in which case
     it does not fsl_free() it).
  */
  void const * allocStamp;
};

/**
   Initialized-with-defaults fsl_deck structure, intended for copy
   initialization.
*/
FSL_EXPORT const fsl_deck fsl_deck_empty;

/**
   Initialized-with-defaults fsl_deck structure, intended for
   in-struct and const copy initialization.
*/
#define fsl_deck_empty_m {                                  \
    FSL_SATYPE_ANY /*type*/,                                \
    0/*rid*/,                                            \
    NULL/*uuid*/,                                         \
    NULL/*f*/,                                            \
    {/*A*/ NULL /* name */,                               \
           NULL /* tgt */,                                   \
           NULL /* src */},                                  \
    {/*B*/ NULL /*uuid*/,                                 \
           NULL /*baseline*/},                               \
    NULL /* C */,                                       \
    0.0 /*D*/,                                        \
    {/*E*/ 0.0 /* julian */,                          \
           NULL /* uuid */},                             \
    /*F*/ fsl_card_F_list_empty_m,  \
    0/*G*/,0/*H*/,0/*I*/,                           \
    fsl_list_empty_m /* J */,                       \
    NULL /* K */,                                 \
    NULL /* L */,                                 \
    fsl_list_empty_m /* M */,                     \
    NULL /* N */,                                 \
    fsl_list_empty_m /* P */,                     \
    fsl_list_empty_m /* Q */,                     \
    NULL /* R */,                                 \
    fsl_list_empty_m /* T */,                     \
    NULL /* U */,                                 \
    fsl_buffer_empty_m /* W */,                   \
    fsl_buffer_empty_m/*content*/,                \
    NULL/*next*/,                                 \
    NULL/*allocStamp*/                            \
  }


/**
   Allocates a new fsl_deck instance. Returns NULL on allocation
   error. The returned value must eventually be passed to
   fsl_deck_finalize() to free its resources.

   @see fsl_deck_finalize()
   @see fsl_deck_clean()
*/
FSL_EXPORT fsl_deck * fsl_deck_malloc();

/**
   Frees all resources belonging to the given deck's members
   (including its parents, recursively), and wipes deck clean of most
   state, but does not free() deck. Is a no-op if deck is NULL. As a
   special case, the (allocStamp, f) members of deck are kept intact.

   @see fsl_deck_finalize()
   @see fsl_deck_malloc()
   @see fsl_deck_clean2()
*/
FSL_EXPORT void fsl_deck_clean(fsl_deck *deck);

/**
   A variant of fsl_deck_clean() which "returns" its content buffer
   for re-use by transferring, after ensuring proper cleanup of its
   internals, its own content buffer's bytes into the given target
   buffer. Note that decks created "manually" do not have any content
   buffer contents, but those loaded via fsl_deck_load_rid() do. This
   function will fsl_buffer_swap() the contents of the given buffer
   (if any) with its own buffer, clean up its newly-acquired memory
   (tgt's previous contents, if any), and fsl_buffer_reuse() the
   output buffer.

   If tgt is NULL, this behaves exactly like fsl_deck_clean().
*/
FSL_EXPORT void fsl_deck_clean2(fsl_deck *deck, fsl_buffer *tgt);

/**
   Frees all memory owned by deck (see fsl_deck_clean()).  If deck
   was allocated using fsl_deck_malloc() then this function
   fsl_free()'s it, otherwise it does not free it.

   @see fsl_deck_malloc()
   @see fsl_deck_clean()
*/
FSL_EXPORT void fsl_deck_finalize(fsl_deck *deck);

/**
   Sets the A-card for an Attachment (FSL_SATYPE_ATTACHMENT)
   deck. Returns 0 on success.

   Returns FSL_RC_MISUSE if any of (mf, filename, target) are NULL,
   FSL_RC_RANGE if !*filename or if uuidSrc is not NULL and
   fsl_is_uuid(uuidSrc) returns false.

   Returns FSL_RC_TYPE if mf is not (as determined by its mf->type
   member) of a deck type capable of holding 'A' cards. (Only decks
   of type FSL_SATYPE_ATTACHMENT may hold an 'A' card.) If uuidSrc
   is NULL or starts with a NUL byte then it is ignored, otherwise
   the same restrictions apply to it as to target.

   The target parameter represents the "name" of the
   wiki/ticket/event record to which the attachment applies. For
   wiki pages this is their normal name (e.g. "MyWikiPage"). For
   events and tickets it is their full 40-byte UUID.

   uuidSrc is the UUID of the attachment blob itself. If it is NULL
   or empty then this card indicates that the attachment will be
   "deleted" (insofar as anything is ever deleted in Fossil).
*/
FSL_EXPORT int fsl_deck_A_set( fsl_deck * mf, char const * filename,
                               char const * target,
                               fsl_uuid_cstr uuidSrc);

/**
   Sets or unsets (if uuidBaseline is NULL or empty) the B-card for
   the given manifest to a copy of the given UUID. Returns 0 on
   success, FSL_RC_MISUSE if !mf, FSL_RC_OOM on allocation
   error. Setting this will free any prior values in mf->B, including
   a previously loaded mf->B.baseline.

   If uuidBaseline is not NULL and fsl_is_uuid() returns false,
   FSL_RC_SYNTAX is returned. If it is NULL the current value is
   freed (semantically, though the deck may still own the memory), the
   B card is effectively removed, and 0 is returned.

   Returns FSL_RC_TYPE if mf is not syntactically allowed to have
   this card card (as determined by
   fsl_card_is_legal(mf->type,...)).

   Sidebar: the ability to unset this card is unusual within this API,
   and is a requirement the library-internal delta manifest creation
   process. Most of the card-setting APIs, even when they are
   described as working like this one, do not accept NULL hash values.
*/
FSL_EXPORT int fsl_deck_B_set( fsl_deck * mf, fsl_uuid_cstr uuidBaseline);

/**
   Semantically identical to fsl_deck_B_set() but sets the C-card and
   does not place a practical limit on the comment's length.  comment
   must be the comment text for the change being applied.  If the
   given length is negative, fsl_strlen() is used to determine its
   length.
*/
FSL_EXPORT int fsl_deck_C_set( fsl_deck * mf, char const * comment, fsl_int_t cardLen);

/**
   Sets mf's D-card as a Julian Date value. Returns FSL_RC_MISUSE if
   !mf, FSL_RC_RANGE if date is negative, FSL_RC_TYPE if a D-card is
   not valid for the given deck, else 0. Passing a value of 0
   effectively unsets the card.
*/
FSL_EXPORT int fsl_deck_D_set( fsl_deck * mf, double date);

/**
   Sets the E-card in the given deck. date may not be negative -
   use fsl_db_julian_now() or fsl_julian_now() to get a default
   time if needed.  Retursn FSL_RC_MISUSE if !mf or !uuid,
   FSL_RC_RANGE if date is not positive, FSL_RC_RANGE if uuid is
   not a valid UUID string.

   Note that the UUID for an event, unlike most other UUIDs, need
   not be calculated - it may be a random hex string, but it must
   pass the fsl_is_uuid() test. Use fsl_db_random_hex() to generate
   random UUIDs. When editing events, e.g. using the HTML UI, only
   the most recent event with the same UUID is shown. So when
   updating events, be sure to apply the same UUID to the edited
   copies before saving them.
*/
FSL_EXPORT int fsl_deck_E_set( fsl_deck * mf, double date, fsl_uuid_cstr uuid);

/**
   Adds a new F-card to the given deck. The uuid argument is required
   to be NULL or pass the fsl_is_uuid() test. The name must be a
   "simplified path name" (as determined by fsl_is_simple_pathname()),
   or FSL_RC_RANGE is returned. Note that a NULL uuid is only valid
   when constructing a delta manifest, and this routine will return
   FSL_RC_MISUSE and update d->f's error state if uuid is NULL and
   d->B.uuid is also NULL.

   perms should be one of the fsl_fileperm_e values (0 is the usual
   case).

   priorName must only be non-NULL when renaming a file, and it must
   follow the same naming rules as the name parameter.

   Returns 0 on success.

   @see fsl_deck_F_set()
*/
FSL_EXPORT int fsl_deck_F_add( fsl_deck * d, char const * name,
                               fsl_uuid_cstr uuid,
                               fsl_fileperm_e perm, 
                               char const * priorName);

/**
   Works mostly like fsl_deck_F_add() except that:

   1) It enables replacing an existing F-card with a new one matching
   the same name.

   2) It enables removing an F-card by passing a NULL uuid.

   3) It refuses to work on a deck for which d->uuid is not NULL or
   d->rid!=0, returning FSL_RC_MISUSE if either of those apply.

   If d contains no F-card matching the given name (case-sensitivity
   depends on d->f's fsl_cx_is_case_sensitive() value) then:

   - If the 3rd argument is NULL, it returns FSL_RC_NOT_FOUND with
     (effectively) no side effects (aside, perhaps, from sorting d->F
     if needed to perform the search).

   - If the 3rd argument is not NULL then it behaves identically to
     fsl_deck_F_add().

   If a match is found, then:

   - If the 3rd argument is NULL, it removes that entry from the
     F-card list and returns 0.

   - If the 3rd argument is not NULL, the fields of the resulting
     F-card are modified to match the arguments passed to this
     function, copying the values of all C-string arguments. (Sidebar:
     we may need to copy the name, despite already knowing it, because
     of how fsl_deck instances manage F-card memory.)

   In all cases, if the 3rd argument is NULL then the 4th and 5th
   arguments are ignored.

   Returns 0 on success, FSL_RC_OOM if an allocation fails.  See
   fsl_deck_F_add() for other failure modes. On error, d's F-card list
   may be left in an inconsistent state and it must not be used
   further.

   @see fsl_deck_F_add()
   @see fsl_deck_F_set_content()
*/
FSL_EXPORT int fsl_deck_F_set( fsl_deck * d, char const * name,
                               fsl_uuid_cstr uuid,
                               fsl_fileperm_e perm, 
                               char const * priorName);
/**
   UNDER CONSTRUCTION! EXPERIMENTAL!

   This variant of fsl_deck_F_set() accepts a buffer of content to
   store as the file's contents. Its hash is derived from that
   content, using fsl_repo_blob_lookup() to hash the given content.
   Thus this routine can replace existing F-cards and save their
   content at the same time. When doing so, it will try to make the
   parent version (if this is a replacement F-card) a delta of the new
   content version (it may refuse to do so for various resources, but
   any heuristics which forbid that will not trigger an error).

   The intended use of this routine is for adding or replacing content
   in a deck which has been prepared using fsl_deck_derive().

   Returns 0 on success, else an error code propagated by
   fsl_deck_F_set(), fsl_repo_blob_lookup(), or some other lower-level
   routine. This routine requires that a transaction is active and
   returns FSL_RC_MISUSE if none is active. For any non-trivial
   error's, d->f's error state will be updated with a description of
   the problem.

   TODO: add a fsl_cx-level or fsl_deck-level API for marking content
   saved this way as private. This type of content is intended for use
   cases which do not have a checkout, and thus cannot be processed
   with fsl_checkin_commit() (which includes a flag to mark its
   content as private).

   @see fsl_deck_F_set()
   @see fsl_deck_F_add()
   @see fsl_deck_derive()
*/
FSL_EXPORT int fsl_deck_F_set_content( fsl_deck * d, char const * name,
                                       fsl_buffer const * src,
                                       fsl_fileperm_e perm, 
                                       char const * priorName);

/**
   UNDER CONSTRUCTION! EXPERIMENTAL!

   This routine rewires d such that it becomes the basis for a derived
   version of itself. Requires that d be a loaded
   from a repository, complete with a UUID and an RID, else
   FSL_RC_MISUSE is returned.

   In short, this function peforms the following:

   - Clears d->P
   - Moves d->uuid into d->P
   - Clears d->rid
   - Clears any other members which need to be (re)set by the new
     child/derived version.
   - It specifically keeps d->F intact OR creates a new one (see below).

   Returns 0 on success, FSL_RC_OOM on an allocation error,
   FSL_RC_MISUSE if d->uuid is NULL or d->rid<=0. If d->type is not
   FSL_SATYPE_CHECKIN, FSL_RC_TYPE is returned. On error, d may be
   left in an inconsistent state and must not be used further except
   to pass it to fsl_deck_finalize().

   The intention of this function is to simplify creation of decks
   which are to be used for creating checkins without requiring a
   checkin.

   To avoid certain corner cases, this function does not allow
   creation of delta manifests. If d has a B-card then it is a delta.
   This function clears its B-card and recreates the F-card list using
   the B-card's F-card list and any F-cards from the current delta. In
   other words, it creates a new baseline manifest.

   TODO: extend this to support other inheritable deck types, e.g.
   wiki, forum posts, and technotes.

   @see fsl_deck_F_set_content()
*/
FSL_EXPORT int fsl_deck_derive(fsl_deck * d);

/**
   Callback type for use with fsl_deck_F_foreach() and
   friends. Implementations must return 0 on success, FSL_RC_BREAK
   to abort looping without an error, and any other value on error.
*/
typedef int (*fsl_card_F_visitor_f)(fsl_card_F const * fc,
                                     void * state);

/**
   For each F-card in d, cb(card,visitorState) is called. Returns
   the result of that loop. If cb returns FSL_RC_BREAK, the
   visitation loop stops immediately and this function returns
   0. If cb returns any other non-0 code, looping stops and that
   code is returned immediately.

   This routine calls fsl_deck_F_rewind() to reset the F-card cursor
   and/or load d's baseline manifest (if any). If loading the baseline
   fails, an error code from fsl_deck_baseline_fetch() is returned.

   The F-cards will be visited in the order they are declared in
   d. For loaded-from-a-repo manifests this is always lexical order
   (for delta manifests, consistent across the delta and
   baseline). For hand-created decks which have not yet been
   fsl_deck_unshuffle()'d, the order is unspecified.
*/
FSL_EXPORT int fsl_deck_F_foreach( fsl_deck * d, fsl_card_F_visitor_f cb,
                                   void * visitorState );

/**
   Fetches the next F-card entry from d. fsl_deck_F_rewind() must
   have be successfully executed one time before calling this, as
   that routine ensures that the baseline is loaded (if needed),
   which is needed for proper iteration over delta manifests.

   This routine always assigns *f to NULL before starting its work, so
   the client can be assured that it will never contain the same value
   as before calling this (unless that value was NULL).

   On success 0 is returned and *f is assigned to the next F-card.
   If *f is NULL when returning 0 then the end of the list has been
   reached (fsl_deck_F_rewind() can be used to re-set it).

   Example usage:

   @code
   int rc;
   fsl_card_F const * fc = NULL;
   rc = fsl_deck_F_rewind(d);
   if(!rc) while( !(rc=fsl_deck_F_next(d, &fc)) && fc) {...}
   @endcode

   Note that files which were deleted in a given version are not
   recorded in baseline manifests but are in deltas. To avoid
   inconsistencies, this routine does NOT include deleted files in its
   results, regardless of whether d is a baseline or delta. (It used
   to, but that turned out to be a design flaw.)

   Implementation notes: for baseline manifests this is a very
   fast and simple operation. For delta manifests it gets
   rather complicated.
*/
FSL_EXPORT int fsl_deck_F_next( fsl_deck * d, fsl_card_F const **f );

/**
   Rewinds d's F-card traversal iterator and loads d's baseline
   manifest, if it has one (i.e. if d->B.uuid is not NULL) and it is
   not loaded already (i.e. if d->B.baseline is NULL). Returns 0 on
   success. The only error condition is if loading of the a baseline
   manifest fails, noting that only delta manifests have baselines.

   Results are undefined if d->f is NULL, and that may trigger an
   assert() in debug builds.
*/
FSL_EXPORT int fsl_deck_F_rewind( fsl_deck * d );

/**
   Looks for a file in a manifest or (for a delta manifest) its
   baseline. No normalization of the given filename is performed -
   it is assumed to be relative to the root of the checkout.

   It requires that d->type be FSL_SATYPE_CHECKIN and that d be
   loaded from a stored manifest or have been fsl_deck_unshuffle()'d
   (if called on an under-construction deck). Specifically, this
   routine requires that d->F be sorted properly or results are
   undefined.

   d->f is assumed to be the fsl_cx instance which deck was loaded
   from, which impacts the search process as follows:

   - The search take's d->f's underlying case-insensitive option into
   account. i.e. if case-insensitivy is on then files in any case
   will match.

   - If no match is found in d and is a delta manifest (d->B.uuid
   is set) then d's baseline is lazily loaded (if needed) and
   the search continues there. (Delta manifests are only one level
   deep, so this is not recursive.)

   Returns NULL if !d, !d->f, or d->type!=FSL_SATYPE_CHECKIN, if no
   entry is found, or if delayed loading of the parent manifest (if
   needed) of a delta manifest fails (in which case d->f's error
   state should hold more information about the problem).

   In debug builds this function asserts that d is not NULL.

   Design note: d "should" be const, but search optimizations for
   the typical use case require potentially lazy-loading
   d->B.baseline and updating d->F.
*/
FSL_EXPORT fsl_card_F const * fsl_deck_F_search(fsl_deck *d, const char *zName);

/**
   Given two F-card instances, this function compares their names
   (case-insensitively). Returns a negative value if lhs is
   lexically less than rhs, a positive value if lhs is lexically
   greater than rhs, and 0 if they are lexically equivalent (or are
   the same pointer).

   Results are undefined if either argument is NULL.
*/
FSL_EXPORT int fsl_card_F_compare( fsl_card_F const * lhs,
                                   fsl_card_F const * rhs);

/**
   If fc->uuid refers to a blob in f's repository database then that
   content is placed into dest (as per fsl_content_get()) and 0 is
   returned. Returns FSL_RC_NOT_FOUND if fc->uuid is not
   found. Returns FSL_RC_MISUSE if any argument is NULL.  If
   fc->uuid is NULL (indicating that it refers to a file deleted in
   a delta manifest) then FSL_RC_RANGE is returned. Returns
   FSL_RC_NOT_A_REPO if f has no repository opened.

   On any error but FSL_RC_MISUSE (basic argument validation) f's
   error state is updated to describe the error.

   @see fsl_content_get()
*/
FSL_EXPORT int fsl_card_F_content( fsl_cx * f, fsl_card_F const * fc,
                                   fsl_buffer * dest );

/**
   Sets the 'G' card on a forum-post deck to a copy of the given
   UUID.
*/
FSL_EXPORT int fsl_deck_G_set( fsl_deck * mf, fsl_uuid_cstr uuid);
/**
   Sets the 'H' card on a forum-post deck to a copy of the given
   comment. If cardLen is negative then fsl_strlen() is used to
   calculate its length.
 */
FSL_EXPORT int fsl_deck_H_set( fsl_deck * mf, char const * comment, fsl_int_t cardLen);
/**
   Sets the 'I' card on a forum-post deck to a copy of the given
   UUID.
*/
FSL_EXPORT int fsl_deck_I_set( fsl_deck * mf, fsl_uuid_cstr uuid);

/**
   Adds a J-card to the given deck, setting/updating the given ticket
   property key to the given value. The key is required but the value
   is optional (may be NULL). If isAppend then the value is appended
   to any existing value, otherwise it replaces any existing value.

   It is currently unclear whether it is legal to include multiple
   J cards for the same key in the same control artifact, in
   particular if their isAppend values differ.

   Returns 0 on success, FSL_RC_MISUSE if !mf or !key, FSL_RC_RANGE
   if !*field, FSL_RC_TYPE if mf is of a type for which J cards are
   not legal (see fsl_card_is_legal()), FSL_RC_OOM on allocation
   error.
*/
FSL_EXPORT int fsl_deck_J_add( fsl_deck * mf, char isAppend,
                               char const * key, char const * value );

/**
   Semantically identical fsl_deck_B_set() but sets the K-card and
   does not accept a NULL value.  uuid must be the UUID of the ticket
   this change is being applied to.
*/
FSL_EXPORT int fsl_deck_K_set( fsl_deck * mf, fsl_uuid_cstr uuid);

/**
   Semantically identical fsl_deck_B_set() but sets the L-card.
   title must be the wiki page title text of the wiki page this
   change is being applied to.
*/
FSL_EXPORT int fsl_deck_L_set( fsl_deck * mf, char const *title, fsl_int_t len);

/**
   Adds the given UUID as an M-card entry. Returns 0 on success, or:

   FSL_RC_MISUSE if !mf or !uuid

   FSL_RC_TYPE if fsl_deck_check_type(mf,'M') returns false.

   FSL_RC_RANGE if !fsl_is_uuid(uuid).

   FSL_RC_OOM if memory allocation fails while adding the entry.
*/
FSL_EXPORT int fsl_deck_M_add( fsl_deck * mf, fsl_uuid_cstr uuid );

/**
   Semantically identical to fsl_deck_B_set() but sets the N card.
   mimeType must be the content mime type for comment text of the
   change being applied.
*/
FSL_EXPORT int fsl_deck_N_set( fsl_deck * mf, char const *mimeType, fsl_int_t len);

/**
   Adds the given UUID as a parent of the given change record. If len
   is less than 0 then fsl_strlen(parentUuid) is used to determine
   its length. Returns FSL_RC_MISUE if !mf, !parentUuid, or
   !*parentUuid. Returns FSL_RC_RANGE if parentUuid is not 40
   bytes long.

   The first P-card added to a deck MUST be the UUID of its primary
   parent (one which was not involved in a merge operation). All
   others (from merges) are considered "non-primary."

*/
FSL_EXPORT int fsl_deck_P_add( fsl_deck * mf, fsl_uuid_cstr parentUuid);

/**
   If d contains a P card with the given index, this returns the
   RID corresponding to the UUID at that index. Returns a negative
   value on error, 0 if there is no for that index or the index
   is out of bounds.
*/
FSL_EXPORT fsl_id_t fsl_deck_P_get_id(fsl_deck * d, int index);

/**
   Adds a Q-card record to the given deck. The type argument must
   be negative for a backed-out change, positive for a cherrypicked
   change.  target must be a valid UUID string. If baseline is not
   NULL then it also must be a valid UUID.

   Returns 0 on success, non-0 on error. FSL_RC_MISUSE if !mf
   or !target, FSL_RC_RANGE if target/baseline are not valid
   UUID strings (baseline may be NULL).
*/
FSL_EXPORT int fsl_deck_Q_add( fsl_deck * mf, int type,
                    fsl_uuid_cstr target,
                    fsl_uuid_cstr baseline );

/**
   Functionally identical to fsl_deck_B_set() except that it sets
   the R-card. Returns 0 on succes, FSL_RC_RANGE if md5 is not NULL
   or exactly FSL_STRLEN_MD5 bytes long (not including trailing
   NUL). If md5==NULL the current R value is cleared.

   It would be highly unusual to have to set the R-card manually,
   as its calculation is quite intricate/intensive. See
   fsl_deck_R_calc() and fsl_deck_unshuffle() for details
*/
FSL_EXPORT int fsl_deck_R_set( fsl_deck * mf, char const *md5);

/**
   Adds a new T-card (tag) entry to the given deck.

   If uuid is not NULL and fsl_is_uuid(uuid) returns false then
   this function returns FSL_RC_RANGE. If uuid is NULL then it is
   assumed to be the UUID of the currently-being-constructed
   artifact in which the tag is contained (which appears as the '*'
   character in generated artifacts).

   Returns 0 on success. Returns FSL_RC_MISUE if !mf or
   !name. Returns FSL_RC_TYPE (and update's mf's error state with a
   message) if the T card is not legal for mf (see
   fsl_card_is_legal()).  Returns FSL_RC_RANGE if !*name, tagType
   is invalid, or if uuid is not NULL and fsl_is_uuid(uuid)
   return false. Returns FSL_RC_OOM if an allocation fails.
*/
FSL_EXPORT int fsl_deck_T_add( fsl_deck * mf, fsl_tagtype_e tagType,
                               fsl_uuid_cstr uuid, char const * name,
                               char const * value);

/**
   Adds the given tag instance to the given manifest.
   Returns 0 on success, FSL_RC_MISUSE if either argument
   is NULL, FSL_RC_OOM if appending the tag to the list
   fails.

   On success ownership of t is passed to mf. On error ownership is
   not modified.
*/
FSL_EXPORT int fsl_deck_T_add2( fsl_deck * mf, fsl_card_T * t);

/**
   A convenience form of fsl_deck_T_add() which adds two propagating
   tags to the given deck: "branch" with a value of branchName and
   "sym-branchName" with no value.

   Returns 0 on success. Returns FSL_RC_OOM on allocation error and
   FSL_RC_RANGE if branchName is empty or contains any characters with
   ASCII values <=32d. It natively assumes that any characters >=128
   are part of multibyte UTF8 characters.
*/
FSL_EXPORT int fsl_deck_branch_set( fsl_deck * d, char const * branchName );

/**
   Calculates the value of d's R-card based on its F-cards and updates
   d->R. It may also, as a side-effect, sort d->F.list lexically (a
   requirement of a R-card calculation).

   Returns 0 on success. Requires that d->f have an opened
   repository db, else FSL_RC_NOT_A_REPO is returned. If d's type is
   not legal for an R-card then FSL_RC_TYPE is returned and d->f's
   error state is updated with a description of the error. If d is of
   type FSL_SATYPE_CHECKIN and has no F-cards then the R-card's value
   is that of the initial MD5 hash state. Various other codes can be
   returned if fetching file content from the db fails.

   Note that this calculation is exceedingly memory-hungry. While
   Fossil originally required R-cards, the cost of calculation
   eventually caused the R-card to be made optional. This API
   allows the client to decide on whether to use them (for more
   (admittedly redundant!) integrity checking) or not (much faster
   but "not strictly historically correct"), but defaults to having
   them enabled for symmetry with fossil(1).

   @see fsl_deck_R_calc2()
*/
FSL_EXPORT int fsl_deck_R_calc(fsl_deck * d);

/**
   A variant of fsl_deck_R_calc() which calculates the given deck's
   R-card but does not assign it to the deck, instead returning it
   via the 2nd argument:

   If *tgt is not NULL when this function is called, it is required to
   point to at least FSL_STRLEN_MD5+1 bytes of memory to which the
   NUL-terminated R-card hash will be written. If *tgt is NULL then
   this function assigns (on success) *tgt to a dynamically-allocated
   R-card hash and transfers ownership of it to the caller (who must
   eventually fsl_free() it). On error, *tgt is not modified.

   Results are undefined if either argument is NULL.
   
   Returns 0 on success. See fsl_deck_R_calc() for information about
   possible errors, with the addition that FSL_RC_OOM is returned
   if *tgt is NULL and allocating a new *tgt value fails.

   Calculating the R-card necessarily requires that d's F-card list be
   sorted, which this routine does if it seems necessary. The
   calculation also necessarily mutates the deck's F-card-traversal
   cursor, which requires loading the deck's B-card, if it has
   one. Aside from the F-card sorting, and potentially B-card, and the
   cursor resets, this routine does not modify the deck. On success,
   the deck's F-card iteration cursor (and that of d->B, if it's
   loaded) is rewound.
*/
FSL_EXPORT int fsl_deck_R_calc2(fsl_deck *d, char ** tgt);

/**
   Semantically identical fsl_deck_B_set() but sets the U-card.
   userName must be the user who's name should be recorded for
   this change.
*/
FSL_EXPORT int fsl_deck_U_set( fsl_deck * mf, char const *userName);

/**
   Semantically identical fsl_deck_B_set() but sets the W-card.
   content must be the page content of the Wiki page or Event this
   change is being applied to.
*/
FSL_EXPORT int fsl_deck_W_set( fsl_deck * mf, char const *content, fsl_int_t len);

/**
   Must be called to initialize a newly-created/allocated deck
   instance. This function clears out all contents of the d
   parameter except for its (f, type, allocStamp) members, sets its
   (f, type) members, and leaves d->allocStamp intact.
*/
FSL_EXPORT void fsl_deck_init( fsl_cx * cx, fsl_deck * d, fsl_satype_e type );

/**
   Returns true if d contains data for all _required_ cards, as
   determined by the value of d->type, else returns false. It returns
   false if d->type==FSL_SATYPE_ANY, as that is a placeholder value
   intended to be re-set by the deck's user.

   If it returns false, d->f's error state will help a description of
   the problem.

   The library calls this as needed, but clients may, if they want
   to. Note, however, that for FSL_SATYPE_CHECKIN decks it may fail
   if the deck has not been fsl_deck_unshuffle()d yet because the
   R-card gets calculated there (if needed).

   As a special case, d->f is configured to calculate R-cards,
   d->type==FSL_SATYPE_CHECKIN, AND d->R is not set, this will fail
   (with a descriptive error message).

   Another special case: for FSL_SATYPE_CHECKIN decks, if no
   F-cards are in th deck then an R-card is required to avoid a
   potental (admittedly harmless) syntactic ambiguity with
   FSL_SATYPE_CONTROL artifacts. The only legal R-card for a
   checkin with no F-cards has the initial MD5 hash state value
   (defined in the constant FSL_MD5_INITIAL_HASH), and that
   precondition is checked in this routine. fsl_deck_unshuffle()
   recognizes this case and adds the initial-state R-card, so
   clients normally need not concern themselves with this. If d has
   F-cards, whether or not an R-card is required depends on
   whether d->f is configured to require them or not.

   Enough about the R-card. In all other cases not described above,
   R-cards are not required (and they are only ever required on
   FSL_SATYPE_CHECKIN manifests).

   Though fossil(1) does not technically require F-cards in
   FSL_SATYPE_CHECKIN decks, so far none of the Fossil developers
   have found a use for a checkin without F-cards except the
   initial empty checkin. Additionally, a checkin without F-cards
   is potentially syntactically ambiguous (it could be an EVENT or
   ATTACHMENT artifact if it has no F- or R-card). So... this
   library _normally_ requires that CHECKIN decks have at least one
   F-card. This function, however, does not consider F-cards to be
   strictly required.
*/
FSL_EXPORT bool fsl_deck_has_required_cards( fsl_deck const * d );

/**
   Prepares the given deck for output by ensuring that cards
   which need to be sorted are sorted, and it may run some
   last-minute validation checks.

   The cards which get sorted are: F, J, M, Q, T. The P-card list is
   _not_ sorted - the client is responsible for ensuring that the
   primary parent is added to that list first, and after that the
   ordering is largely irrelevant. It is not possible for the library
   to determine a proper order for P-cards, nor to validate that order
   at input-time.

   If calculateRCard is true and fsl_card_is_legal(d,'R') then this
   function calculates the R-card for the deck. The R-card
   calculation is _extremely_ memory-hungry but adds another level
   of integrity checking to Fossil. If d->type is not
   FSL_SATYPE_MANIFEST then calculateRCard is ignored.

   If calculateRCard is true but no F-cards are present AND d->type is
   FSL_SATYPE_CHECKIN then the R-card is set to the initial MD5 hash
   state (the only legal R-card value for an empty F-card list). (This
   is necessary in order to prevent a deck-type ambiguity in one
   corner case.)

   The R-card, if used, must be calculated before
   fsl_deck_output()ing a deck containing F-cards. Clients may
   alternately call fsl_deck_R_calc() to calculate the R card
   separately, but there is little reason to do so. There are rare
   cases where the client can call fsl_deck_R_set()
   legally. Historically speaking the R-card was required when
   F-cards were used, but it was eventually made optional because
   (A) the memory cost and (B) it's part of a 3rd or 4th level of
   integrity-related checks, and is somewhat superfluous.

   @see fsl_deck_output()
   @see fsl_deck_save()
*/
FSL_EXPORT int fsl_deck_unshuffle( fsl_deck * d, bool calculateRCard );

/**
   Renders the given control artifact's contents to the given output
   function and calculates any cards which cannot be calculated until
   the contents are complete (namely the R-card and Z-card).

   The given deck is "logically const" but traversal over F-cards and
   baselines requires non-const operations. To keep this routine from
   requiring an undue amount of pre-call effort on the client's part,
   it also takes care of calling fsl_deck_unshuffle() to ensure that
   all of the deck's cards are in order. (If the deck has no R card,
   but has F-cards, and d->f is configured to generate R-cards, then
   unshuffling will also calculate the R-card.)

   Returns 0 on success, FSL_RC_MISUSE if !d or !d->f or !out. If
   out() returns non-0, output stops and that code is
   returned. outputState is passed as the first argument to out() and
   out() may be called an arbitrary number of times by this routine.

   Returns FSL_RC_SYNTAX if fsl_deck_has_required_cards()
   returns false.

   On errors more serious than argument validation, the deck's
   context's (d->f) error state is updated.

   The exact structure of the ouput depends on the value of
   mf->type, and FSL_RC_TYPE is returned if this function cannot
   figure out what to do with the given deck's type.

   @see fsl_deck_unshuffle()
   @see fsl_deck_save()
*/
FSL_EXPORT int fsl_deck_output( fsl_deck * d, fsl_output_f out,
                                void * outputState );


/**
   Saves the given deck into f's repository database as new control
   artifact content. If isPrivate is true then the content is
   marked as private, otherwise it is not. Note that isPrivate is a
   suggestion and might be trumped by existing state within f or
   its repository, and such a trumping is not treated as an
   error. e.g. tags are automatically private when they tag private
   content.

   Before saving, the deck is passed through fsl_deck_unshuffle()
   and fsl_deck_output(), which will fail for a variety of
   easy-to-make errors such as the deck missing required cards.
   For unshuffle purposes, the R-card gets calculated if the deck
   has any F-cards AND if the caller has not already set/calculated
   it AND if f's FSL_CX_F_CALC_R_CARD flag is set (it is on by
   default for historical reasons, but this may change at some
   point).

   Returns 0 on success, the usual non-0 suspects on error.

   If d->rid and d->uuid are set when this is called, it is assumed
   that we are saving existing or phantom content, and in that
   case:

   - An existing phantom is populated with the new content.

   - If an existing record is found with a non-0 size then it is
   not modified but this is currently not treated as an error (for
   historical reasons, though one could argue that it should result
   in FSL_RC_ALREADY_EXISTS).

   If d->rid and d->uuid are not set when this is called then... on
   success, d->rid and d->uuid will contain the values held by
   their counterparts in the blob table. They will only be set on
   success because they would otherwise refer to db records which
   get destroyed when the transaction rolls back.

   After saving, the deck will be cross-linked to update any
   relationships described by the deck.

   The save operation happens within a transaction, of course, and
   on any sort of error, db-side changes are rolled back. Note that
   it _is_ legal to start the transaction before calling this,
   which effectively makes this operation part of that transaction.

   This function will fail with FSL_RC_ACCESS if d is a delta manifest
   (has a B-card) d->f's forbid-delta-manifests configuration option
   is set to a truthy value. See fsl_repo_forbids_delta_manifests().

   Maintenance reminder: this function also does a small bit of
   artifact-type-specific processing.

   @see fsl_deck_output()
   @see fsl_content_put_ex()
*/
FSL_EXPORT int fsl_deck_save( fsl_deck * d, bool isPrivate );

/**
    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 this function succeeds, the caller is OBLIGATED to either call
    fsl_crosslink_end() or fsl_db_transaction_rollback(), depending
    on whether the work done after this call succeeds
    resp. fails. 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 * f);

/**
    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().

    Returns 0 on success. On error it initiates (or propagates) a
    rollback for the current transaction.
*/
int fsl_crosslink_end(fsl_cx * f);

/**
   Parses src as Control Artifact content and populates d with it.

   d will be cleaned up before parsing if it has any contents,
   retaining its d->f member (which must be non-NULL for
   error-reporting purposes).

   This function _might_ take over the contents of the source
   buffer on success or it _might_ leave it for the caller to clean
   up or re-use, as he sees fit. If the caller does not intend to
   re-use the buffer, he should simply pass it to
   fsl_buffer_clear() after calling this (no need to check if it
   has contents or not first).

   When taking over the contents then on success, after returning
   src->mem will be NULL, and all other members will be reset to
   their default state. This function only takes over the contents
   if it decides to implement certain memory optimizations.

   Ownership of src itself is never changed by this function, only
   (possibly!) the ownership of its contents.

   In any case, the content of the source buffer is modified by
   this function because (A) that simplifies tokenization greatly,
   (B) saves us having to make another copy to work on, (C) the
   original implementation did it that way, (D) because in
   historical use the source is normally thrown away after parsing,
   anyway, and (E) in combination with taking ownership of src's
   contents it allows us to optimize away some memory allocations
   by re-using the internal memory of the buffer. This function
   never changes src's size, but it mutilates its contents
   (injecting NUL bytes as token delimiters).

   If d->type is _not_ FSL_SATYPE_ANY when this is called, then
   this function requires that the input to be of that type. We can
   fail relatively quickly in that case, and this can be used to
   save some downstream code some work. Note that the initial type
   for decks created using fsl_deck_malloc() or copy-initialized
   from ::fsl_deck_empty is FSL_SATYPE_ANY, so normally clients do
   not need to set this (unless they want to, as a small
   optimization).

   On success it returns 0 and d will be updated with the state
   from the input artifact. (Ideally, outputing d via
   fsl_deck_output() will produce a lossless copy of the original.)
   d->uuid will be set to the SHA1 of the input artifact, ignoring
   any surrounding PGP signature for hashing purposes.

   If d->f has an opened repository db and the parsed artifact has a
   counterpart in the database (determined via a hash match) then
   d->rid is set to the record ID.

   On error, if there is error information to propagate beyond the
   result code then it is stored in d->f (if that is not NULL),
   else in d->error. Whether or not such error info is propagated
   depends on the type of error, but anything more trivial than
   invalid arguments will be noted there.

   d might be partially populated on error, so regardless of success
   or failure, the client must eventually pass d to
   fsl_deck_finalize() to free its memory.

   Error result codes include:

   - FSL_RC_MISUSE if any pointer argument is NULL.

   - FSL_RC_SYNTAX on syntax errors.

   - FSL_RC_CONSISTENCY if validation of a Z-card fails.

   - Any number of errors coming from the allocator, database, or
   fsl_deck APIs used here.
*/
FSL_EXPORT int fsl_deck_parse(fsl_deck * d, fsl_buffer * src);

/**
   Quickly determines whether the content held by the given buffer
   "might" be a structural artifact. It performs a fast sanity check
   for prominent features which can be checked either in O(1) or very
   short O(N) time (with a fixed N). If it returns false then the
   given buffer's contents are, with 100% certainty, *not* a
   structural artifact. If it returns true then they *might* be, but
   being 100% certain requires passing the contents to
   fsl_deck_parse() to fully parse them.
*/
FSL_EXPORT bool fsl_might_be_artifact(fsl_buffer const * src);

/**
   Loads the content from given rid and tries to parse it as a
   Fossil artifact. If rid==0 the current checkout (if opened) is
   used. (Trivia: there can never be a checkout with rid==0 but
   rid==0 is sometimes valid for an new/empty repo devoid of
   commits). If type==FSL_SATYPE_ANY then it will allow any type of
   control artifact, else it returns FSL_RC_TYPE if the loaded
   artifact is of the wrong type.

   Returns 0 on success.

   d may be partially populated on error, and the caller must
   eventually pass it to fsl_deck_finalize() resp. fsl_deck_clean()
   regardless of success or error. This function "could" clean it
   up on error, but leaving it partially populated makes debugging
   easier. If the error was an artifact type mismatch then d will
   "probably" be properly populated but will not hold the type of
   artifact requested. It "should" otherwise be well-formed because
   parsing errors occur before the type check can happen, but
   parsing of invalid manifests might also trigger a FSL_RC_TYPE
   error of a different nature. The morale of the storage is: if
   this function returns non-0, assume d is useless and needs to be
   cleaned up.

   f's error state may be updated on error (for anything more
   serious than basic argument validation errors).

   On success d->f is set to f.

   @see fsl_deck_load_sym()
*/
FSL_EXPORT int fsl_deck_load_rid( fsl_cx * f, fsl_deck * d,
                                  fsl_id_t rid, fsl_satype_e type );

/**
   A convenience form of fsl_deck_load_rid() which uses
   fsl_sym_to_rid() to convert symbolicName into an artifact RID.  See
   fsl_deck_load_rid() for the symantics of the first, second, and
   fourth arguments, as well as the return value. See fsl_sym_to_rid()
   for the allowable values of symbolicName.

   @see fsl_deck_load_rid()
*/
FSL_EXPORT int fsl_deck_load_sym( fsl_cx * f, fsl_deck * d,
                                  char const * symbolicName,
                                  fsl_satype_e type );

/**
   Loads the baseline manifest specified in d->B.uuid, if any and if
   necessary. Returns 0 on success. If d->B.baseline is already loaded
   or d->B.uuid is NULL (in which case there is no baseline), it
   returns 0 and has no side effects.

   Neither argument may be NULL and d must be a fully-populated
   object, complete with a proper d->rid, before calling this.

   On success 0 is returned. If d->B.baseline is NULL then
   it means that d has no baseline manifest (and d->B.uuid will be NULL
   in that case). If d->B.baseline is not NULL then it is owned by
   d and will be cleaned up when d is cleaned/finalized.

   Error codes include, but are not limited to:

   - FSL_RC_MISUSE if !d->f.

   - FSL_RC_NOT_A_REPO if d->f has no opened repo db.

   - FSL_RC_RANGE if d->rid<=0, but that code might propagate up from
   a lower-level call as well.

   On non-trivial errors d->f's error state will be updated to hold
   a description of the problem.

   Some misuses trigger assertions in debug builds.
*/
FSL_EXPORT int fsl_deck_baseline_fetch( fsl_deck * d );

/**
   A callback interface for manifest crosslinking, so that we can farm
   out the updating of the event table. Each callback registered via
   fsl_xlink_listener() will be called at the end of the so-called
   crosslinking process, which is run every time a control artifact is
   processed for d->f's repository database, passed the deck being
   crosslinked and the client-provided state which was registered with
   fsl_xlink_listener(). Note that the deck object itself holds other
   state useful for crosslinking, like the blob.rid value of the deck
   and its fsl_cx instance.

   If an implementation is only interested in a specific type of
   artifact, it must check d->type and return 0 if it's an
   "uninteresting" type.

   Implementations must return 0 on success or some other fsl_rc_e
   value on error. Returning non-0 causes the database transaction
   for the crosslinking operation to roll back, effectively
   cancelling whatever pending operation triggered the
   crosslink. If any callback fails, processing stops immediately -
   no other callbacks are executed.

   Implementations which want to report more info than an integer
   should call fsl_cx_err_set() to set d->f's error state, as that
   will be propagated up to the code which initiated the failed
   crosslink.

   ACHTUNG and WARNING: the fsl_deck parameter "really should" be
   const, but certain operations on a deck are necessarily non-const
   operations. That includes, but may not be limited to:

   - Iterating over F-cards, which requires calling
     fsl_deck_F_rewind() before doing so.

   - Loading a checkin's baseline (required for F-card iteration and
   performed automatically by fsl_deck_F_rewind()).

   Aside from such iteration-related mutable state, it is STRICTLY
   ILLEGAL to modify a deck's artifact-related state while it is
   undergoing crosslinking. It is legal to modify its error state.


   Potential TODO: add some client-opaque state to decks so that they
   can be flagged as "being crosslinked" and fail mutation operations
   such as card adders/setters.

   @see fsl_xlink_listener()
*/
typedef int (*fsl_deck_xlink_f)(fsl_deck * d, void * state);

/**
    A type for holding a callback/state pair for manifest
    crosslinking callbacks.
*/
struct fsl_xlinker {
  char const * name;
  /** Callback function. */
  fsl_deck_xlink_f f;
  /** State for this->f's last argument. */
  void * state;
};
typedef struct fsl_xlinker fsl_xlinker;

/** Empty-initialized fsl_xlinker struct, intended for const-copy
    intialization. */
#define fsl_xlinker_empty_m {NULL,NULL,NULL}

/** Empty-initialized fsl_xlinker struct, intended for copy intialization. */
extern const fsl_xlinker fsl_xlinker_empty;

/**
    A list of fsl_xlinker instances.
*/
struct fsl_xlinker_list {
  /** Number of used items in this->list. */
  fsl_size_t used;
  /** Number of slots allocated in this->list. */
  fsl_size_t capacity;
  /** Array of this->used elements. */
  fsl_xlinker * list;
};
typedef struct fsl_xlinker_list fsl_xlinker_list;

/** Empty-initializes fsl_xlinker_list struct, intended for
    const-copy intialization. */
#define fsl_xlinker_list_empty_m {0,0,NULL}

/** Empty-initializes fsl_xlinker_list struct, intended for copy intialization. */
extern const fsl_xlinker_list fsl_xlinker_list_empty;

/**
    Searches f's crosslink callbacks for an entry with the given
    name and returns that entry, or NULL if no match is found.  The
    returned object is owned by f.
*/
fsl_xlinker * fsl_xlinker_by_name( fsl_cx * f, char const * name );

/**
   Adds the given function as a "crosslink callback" for the given
   Fossil context. The callback is called at the end of a
   successfull fsl_deck_crosslink() operation and provides a way
   for the client to perform their own work based on the app having
   crosslinked an artifact. Crosslinking happens when artifacts are
   saved or upon a rebuild operation.

   Crosslink callbacks are called at the end of the core crosslink
   steps, in the order they are registered, with the caveat that if a
   listener is overwritten by another with the same name, the new
   entry retains the older one's position in the list. The library may
   register its own before the client gets a chance to.

   If _any_ crosslinking callback fails (returns non-0) then the
   _whole_ crosslinking fails and is rolled back (which may very
   well include pending tags/commits/whatever getting rolled back).

   The state parameter has no meaning for this function, but is
   passed on as the final argument to cb(). If not NULL, cbState
   "may" be required to outlive f, depending on cbState's exact
   client-side internal semantics/use, as there is currently no API
   to remove registered crosslink listeners.

   The name must be non-NULL/empty. If a listener is registered with a
   duplicate name then the first one is replaced. This function does
   not copy the name bytes - they are assumed to be static or
   otherwise to live at least as long as f. The name may be
   arbitrarily long, but must have a terminating NUL byte. It is
   recommended that clients choose a namespace/prefix to apply to the
   names they register. The library reserves the prefix "fsl/" for
   its own use, and will happily overwrite client-registered entries
   with the same names. The name string need not be stable across
   application sessions and maybe be a randomly-generated string.

   Caveat: some obscure artifact crosslinking steps do not happen
   unless crosslinking takes place in the context of a
   fsl_crosslink_begin() and fsl_crosslink_end()
   session. Thus, at the time client-side crosslinker callbacks are
   called, certain crosslinking state in the database may still be
   pending. It is as yet unclear how best to resolve that minor
   discrepancy, or whether it even needs resolving.


   Default (overrideable) crosslink handlers:

   The library internally splits crosslinking of artifacts into two
   parts: the main one (which clients cannot modify) handles the
   database-level linking of relational state implied by a given
   artifact. The secondary one adds an entry to the "event" table,
   which is where Fossil's timeline lives. The crosslinkers for the
   timeline updates may be overridden by clients by registering
   a crosslink listener with the following names:

   - Attachment artifacts: "fsl/attachment/timeline"

   - Checkin artifacts: "fsl/checkin/timeline"

   - Control artifacts: "fsl/control/timeline"

   - Forum post artifacts: "fsl/forumpost/timeline"

   - Technote artifacts: "fsl/technote/timeline"

   - Wiki artifacts: "fsl/wiki/timeline"

   A context registers listeners under those names when it
   initializes, and clients may override them at any point after that.

   Caveat: updating the timeline requires a bit of knowledge about the
   Fossil DB schema and/or conventions. Updates for certain types,
   e.g. attachment/control/forum post, is somewhat more involved and
   updating the timeline for wiki comments requires observing a "quirk
   of conventions" for labeling such comments, such that they will
   appear properly when the main fossil app renders them. That said,
   the only tricky parts of those updates involve generating the
   "correct" comment text. So long as the non-comment parts are
   updated properly (that part is easy to do), fossil can function
   with it.  The timeline comment text/links are soley for human
   consumption. Fossil makes much use of the "event" table internally,
   however, so the rest of that table must be properly populated.

   Because of that caveat, clients may, rather than overriding the
   defaults, install their own crosslink listners which ammend the
   state applied by the default ones. e.g. add a listener which
   watches for checkin updates and replace the default-installed
   comment with one suitable for your application, leaving the rest of
   the db state in place. At its simplest, that looks more or less
   like the following code (inside a fsl_deck_xlink_f() callback):

   @code
   int rc = fsl_db_exec(fsl_cx_db_repo(deck->f),
                        "UPDATE event SET comment=%Q "
                        "WHERE objid=%"FSL_ID_T_PFMT,
                        "the new comment.", deck->rid);
   @endcode
*/
FSL_EXPORT int fsl_xlink_listener( fsl_cx * f, char const * name,
                                   fsl_deck_xlink_f cb, void * cbState );


/**
   For the given blob.rid value, returns the blob.size value of
   that record via *rv. Returns 0 or higher on success, -1 if a
   phantom record is found, -2 if no entry is found, or a smaller
   negative value on error (dig around the sources to decode them -
   this is not expected to fail unless the system is undergoing a
   catastrophe).

   @see fsl_content_blob()
   @see fsl_content_get()
*/
FSL_EXPORT fsl_int_t fsl_content_size( fsl_cx * f, fsl_id_t blobRid );

/**
   For the given blob.rid value, fetches the content field of that
   record and overwrites tgt's contents with it (reusing tgt's
   memory if it has any and if it can). The blob's contents are
   uncompressed if they were stored in compressed form. This
   extracts a raw blob and does not apply any deltas - use
   fsl_content_get() to fully expand a delta-stored blob.

   Returns 0 on success. On error tgt might be partially updated,
   e.g. it might be populated with compressed data instead of
   uncompressed. On error tgt's contents should be recycled
   (e.g. fsl_buffer_reuse()) or discarded (e.g. fsl_buffer_clear())
   by the client.

   @see fsl_content_get()
   @see fsl_content_size()
*/
FSL_EXPORT int fsl_content_blob( fsl_cx * f, fsl_id_t blobRid, fsl_buffer * tgt );

/**
   Functionally similar to fsl_content_blob() but does a lot of
   work to ensure that the returned blob is expanded from its
   deltas, if any. The tgt buffer's memory, if any, will be
   replaced/reused if it has any.

   Returns 0 on success. There are no less than 50 potental
   different errors, so we won't bother to list them all. On error
   tgt might be partially populated. The basic error cases are:

   - FSL_RC_MISUSE if !tgt or !f.

   - FSL_RC_RANGE if rid<=0 or if an infinite loop is discovered in
   the repo delta table links (that is a consistency check to avoid
   an infinite loop - that condition "cannot happen" because the
   verify-before-commit logic catches that error case).

   - FSL_RC_NOT_A_REPO if f has no repo db opened.

   - FSL_RC_NOT_FOUND if the given rid is not in the repo db.

   - FSL_RC_OOM if an allocation fails.


   @see fsl_content_blob()
   @see fsl_content_size()
*/
FSL_EXPORT int fsl_content_get( fsl_cx * f, fsl_id_t blobRid, fsl_buffer * tgt );

/**
   Uses fsl_sym_to_rid() to convert sym to a record ID, then
   passes that to fsl_content_get(). Returns 0 on success.
*/
FSL_EXPORT int fsl_content_get_sym( fsl_cx * f, char const * sym, fsl_buffer * tgt );

/**
   Returns true if the given rid is marked as PRIVATE in f's current
   repository. Returns false (0) on error or if the content is not
   marked as private.
*/
FSL_EXPORT bool fsl_content_is_private(fsl_cx * f, fsl_id_t rid);

/**
   Marks the given rid public, if it was previously marked as
   private. Returns 0 on success, non-0 on error.

   Note that it is not possible to make public content private.
*/
FSL_EXPORT int fsl_content_make_public(fsl_cx * f, fsl_id_t rid);

/**
   Generic callback interface for visiting decks. The interface
   does not generically require that d survive after this call
   returns.

   Implementations must return 0 on success, non-0 on error. Some
   APIs using this interface may specify that FSL_RC_BREAK can be
   used to stop iteration over a loop without signaling an error.
   In such cases the APIs will translate FSL_RC_BREAK to 0 for
   result purposes, but will stop looping over whatever it is they
   are looping over.
*/
typedef int (*fsl_deck_visitor_f)( fsl_cx * f, fsl_deck const * d,
                                   void * state );

/**
   For each unique wiki page name in f's repostory, this calls
   cb(), passing it the manifest of the most recent version of that
   page. The callback should return 0 on success, FSL_RC_BREAK to
   stop looping without an error, or any other non-0 code
   (preferably a value from fsl_rc_e) on error.

   The 3rd parameter has no meaning for this function but it is
   passed on as-is to the callback.

   ACHTUNG: the deck passed to the callback is transient and will
   be cleaned up after the callback has returned, so the callback
   must not hold a pointer to it or its contents.

   @see fsl_wiki_load_latest()
   @see fsl_wiki_latest_rid()
   @see fsl_wiki_names_get()
   @see fsl_wiki_page_exists()
*/
FSL_EXPORT int fsl_wiki_foreach_page( fsl_cx * f, fsl_deck_visitor_f cb, void * state );

/**
   Fetches the most recent RID for the given wiki page name and
   assigns *newId (if it is not NULL) to that value. Returns 0 on
   success, FSL_RC_MISUSE if !f or !pageName, FSL_RC_RANGE if
   !*pageName, and a host of other potential db-side errors
   indicating more serious problems. If no such page is found,
   newRid is not modified and this function returns 0 (as opposed
   to FSL_RC_NOT_FOUND) because that simplifies usage (so far).

   On error *newRid is not modified.

   @see fsl_wiki_load_latest()
   @see fsl_wiki_foreach_page()
   @see fsl_wiki_names_get()
   @see fsl_wiki_page_exists()
*/
FSL_EXPORT int fsl_wiki_latest_rid( fsl_cx * f, char const * pageName, fsl_id_t * newRid );

/**
   Loads the artifact for the most recent version of the given wiki page,
   populating d with its contents.

   Returns 0 on success. On error d might be partially populated,
   so it needs to be passed to fsl_deck_finalize() regardless of
   whether this function succeeds or fails.

   Returns FSL_RC_NOT_FOUND if no page with that name is found.

   @see fsl_wiki_latest_rid()
   @see fsl_wiki_names_get()
   @see fsl_wiki_page_exists()
*/
FSL_EXPORT int fsl_wiki_load_latest( fsl_cx * f, char const * pageName, fsl_deck * d );

/**
   Returns true (non-0) if f's repo database contains a page with the
   given name, else false.

   @see fsl_wiki_load_latest()
   @see fsl_wiki_latest_rid()
   @see fsl_wiki_names_get()
   @see fsl_wiki_names_get()
*/
FSL_EXPORT bool fsl_wiki_page_exists(fsl_cx * f, char const * pageName);

/**
   A helper type for use with fsl_wiki_save(), intended primarily
   to help client-side code readability somewhat.
*/
enum fsl_wiki_save_mode_t {
/**
   Indicates that fsl_wiki_save() must only allow the creation of
   a new page, and must fail if such an entry already exists.
*/
FSL_WIKI_SAVE_MODE_CREATE = -1,
/**
   Indicates that fsl_wiki_save() must only allow the update of an
   existing page, and will not create a branch new page.
*/
FSL_WIKI_SAVE_MODE_UPDATE = 0,
/**
   Indicates that fsl_wiki_save() must allow both the update and
   creation of pages. Trivia: "upsert" is a common SQL slang
   abbreviation for "update or insert."
*/
FSL_WIKI_SAVE_MODE_UPSERT = 1
};

typedef enum fsl_wiki_save_mode_t fsl_wiki_save_mode_t;

/**
   Saves wiki content to f's repository db.

   pageName is the name of the page to update or create.

   b contains the content for the page.

   userName specifies the user name to apply to the change. If NULL
   or empty then fsl_cx_user_get() or fsl_guess_user_name() are
   used (in that order) to determine the name.

   mimeType specifies the mime type for the content (may be NULL).
   Mime type names supported directly by fossil(1) include (as of
   this writing): text/x-fossil-wiki, text/x-markdown,
   text/plain

   Whether or not this function is allowed to create a new page is
   determined by creationPolicy. If it is
   FSL_WIKI_SAVE_MODE_UPDATE, this function will fail with
   FSL_RC_NOT_FOUND if no page with the given name already exists.
   If it is FSL_WIKI_SAVE_MODE_CREATE and a previous version _does_
   exist, it fails with FSL_RC_ALREADY_EXISTS. If it is
   FSL_WIKI_SAVE_MODE_UPSERT then both the save-exiting and
   create-new cases are allowed. In summary:

   - use FSL_WIKI_SAVE_MODE_UPDATE to allow updates to existing pages
   but disallow creation of new pages,

   - use FSL_WIKI_SAVE_MODE_CREATE to allow creating of new pages
   but not of updating an existing page.

   - FSL_WIKI_SAVE_MODE_UPSERT allows both updating and creating
   a new page on demand.

   Returns 0 on success, or any number fsl_rc_e codes on error. On
   error no content changes are saved, and any transaction is
   rolled back or a rollback is scheduled if this function is
   called while a transaction is active.


   Potential TODO: add an optional (fsl_id_t*) output parameter
   which gets set to the new record's RID.

   @see fsl_wiki_page_exists()
   @see fsl_wiki_names_get()
*/
FSL_EXPORT int fsl_wiki_save(fsl_cx * f, char const * pageName,
                  fsl_buffer const * b, char const * userName,
                  char const * mimeType, fsl_wiki_save_mode_t creationPolicy );

/**
   Fetches the list of all wiki page names in f's current repo db
   and appends them as new (char *) strings to tgt. On error tgt
   might be partially populated (but this will only happen on an
   OOM or serious system-level error).

   It is up to the caller free the entries added to the list. Some
   of the possibilities include:

   @code
   fsl_list_visit( list, 0, fsl_list_v_fsl_free, NULL );
   fsl_list_reserve(list,0);
   // Or:
   fsl_list_clear(list, fsl_list_v_fsl_free, NULL);
   // Or simply:
   fsl_list_visit_free( list, 1 );
   @endcode

*/
FSL_EXPORT int fsl_wiki_names_get( fsl_cx * f, fsl_list * tgt );

/**
   F-cards each represent one file entry in a Manifest Artifact (i.e.,
   a checkin version).

   All of the non-const pointers in this class are owned by the
   respective instance of the class OR by the fsl_deck which created
   it, and must neither be modified nor freed except via the
   appropriate APIs.
*/
struct fsl_card_F {
  /**
     UUID of the underlying blob record for the file. NULL for
     removed entries.
  */
  fsl_uuid_str uuid;
  /**
     Name of the file.
  */
  char * name;
  /**
     Previous name if the file was renamed, else NULL.
  */
  char * priorName;
  /**
     File permissions. Fossil only supports one "permission" per
     file, and it does not necessarily map to a real
     filesystem-level permission.

     @see fsl_fileperm_e
  */
  fsl_fileperm_e perm;

  /**
     An internal optimization. Do not mess with this.  When this is
     true, the various string members of this struct are not owned
     by this struct, but by the deck which created this struct. This
     is used when loading decks from storage - the strings are
     pointed to the original content data, rather than strdup()'d
     copies of it. fsl_card_F_clean() will DTRT and delete the
     strings (or not).
  */
  bool deckOwnsStrings;
};
/**
   Empty-initialized fsl_card_F structure, intended for use in
   initialization when embedding fsl_card_F in another struct or
   copy-initializing a const struct.
*/
#define fsl_card_F_empty_m {   \
  NULL/*uuid*/,                \
  NULL/*name*/,                \
  NULL/*priorName*/,           \
  0/*perm*/,                   \
  false/*deckOwnsStrings*/     \
}
FSL_EXPORT const fsl_card_F fsl_card_F_empty;

/**
   Represents a J card in a Ticket Control Artifact.
*/
struct fsl_card_J {
  /**
     If true, the new value should be appended to any existing one
     with the same key, else it will replace any old one.
  */
  char append;
  /**
     For internal use only.
  */
  unsigned char flags;
  /**
     The ticket field to update. The bytes are owned by this object.
  */
  char * field;
  /**
     The value for the field. The bytes are owned by this object.
  */
  char * value;
};
/** Empty-initialized fsl_card_J struct. */
#define fsl_card_J_empty_m {0,0,NULL, NULL}
/** Empty-initialized fsl_card_J struct. */
FSL_EXPORT const fsl_card_J fsl_card_J_empty;

/**
   Represents a tag in a Manifest or Control Artifact.
*/
struct fsl_card_T {
  /**
     The type of tag.
  */
  fsl_tagtype_e type;
  /**
     UUID of the artifact this tag is tagging. When applying a tag to
     a new checkin, this value is left empty (=NULL) and gets replaced
     by a '*' in the resulting control artifact.
  */
  fsl_uuid_str uuid;
  /**
     The tag's name. The bytes are owned by this object.
  */
  char * name;
  /**
     The tag's value. May be NULL/empty. The bytes are owned by
     this object.
  */
  char * value;
};
/** Defaults-initialized fsl_card_T instance. */
#define fsl_card_T_empty_m {FSL_TAGTYPE_INVALID, NULL, NULL,NULL}
/** Defaults-initialized fsl_card_T instance. */
FSL_EXPORT const fsl_card_T fsl_card_T_empty;

/**
   Types of cherrypick merges.
*/
enum fsl_cherrypick_type_e {
/** Sentinel value. */
FSL_CHERRYPICK_INVALID = 0,
/** Indicates a cherrypick merge. */
FSL_CHERRYPICK_ADD = 1,
/** Indicates a cherrypick backout. */
FSL_CHERRYPICK_BACKOUT = -1
};
typedef enum fsl_cherrypick_type_e fsl_cherrypick_type_e;

/**
   Represents a Q card in a Manifest or Control Artifact.
*/
struct fsl_card_Q {
  /** 0==invalid, negative==backed out, positive=cherrypicked. */
  fsl_cherrypick_type_e type;
  /**
     UUID of the target of the cherrypick. The bytes are owned by
     this object.
  */
  fsl_uuid_str target;
  /**
     UUID of the baseline for the cherrypick. The bytes are owned by
     this object.
  */
  fsl_uuid_str baseline;
};
/** Empty-initialized fsl_card_Q struct. */
#define fsl_card_Q_empty_m {FSL_CHERRYPICK_INVALID, NULL, NULL}
/** Empty-initialized fsl_card_Q struct. */
FSL_EXPORT const fsl_card_Q fsl_card_Q_empty;

/**
   Allocates a new J-card record instance

   On success it returns a new record which must eventually be
   passed to fsl_card_J_free() to free its resources. On
   error (invalid arguments or allocation error) it returns NULL.
   field may not be NULL or empty but value may be either.

   These records are immutable - the API provides no way to change
   them once they are instantiated.
*/
FSL_EXPORT fsl_card_J * fsl_card_J_malloc(bool isAppend,
                                          char const * field,
                                          char const * value);
/**
   Frees a J-card record created by fsl_card_J_malloc().
   Is a no-op if cp is NULL.
*/
FSL_EXPORT void fsl_card_J_free( fsl_card_J * cp );

/**
   Allocates a new fsl_card_T instance. If any of the pointer
   parameters are non-NULL, their values are assumed to be
   NUL-terminated strings, which this function copies.  Returns NULL
   on allocation error.  The returned value must eventually be passed
   to fsl_card_T_clean() or fsl_card_T_free() to free its resources.

   If uuid is not NULL and fsl_is_uuid(uuid) returns false then
   this function returns NULL. If it is NULL and gets assigned
   later, it must conform to fsl_is_uuid()'s rules or downstream
   results are undefined.

   @see fsl_card_T_free()
   @see fsl_card_T_clean()
   @see fsl_deck_T_add()
*/
FSL_EXPORT fsl_card_T * fsl_card_T_malloc(fsl_tagtype_e tagType,
                                          fsl_uuid_cstr uuid,
                                          char const * name,
                                          char const * value);
/**
   If t is not NULL, calls fsl_card_T_clean(t) and then passes t to
   fsl_free().

   @see fsl_card_T_clean()
*/
FSL_EXPORT void fsl_card_T_free(fsl_card_T *t);

/**
   Frees up any memory owned by t and clears out t's state,
   but does not free t.

   @see fsl_card_T_free()
*/
FSL_EXPORT void fsl_card_T_clean(fsl_card_T *t);

/**
   Allocates a new cherrypick record instance. The type argument must
   be one of FSL_CHERRYPICK_ADD or FSL_CHERRYPICK_BACKOUT.  target
   must be a valid UUID string. If baseline is not NULL then it also
   must be a valid UUID.

   On success it returns a new record which must eventually be
   passed to fsl_card_Q_free() to free its resources. On
   error (invalid arguments or allocation error) it returns NULL.

   These records are immutable - the API provides no way to change
   them once they are instantiated.
*/
FSL_EXPORT fsl_card_Q * fsl_card_Q_malloc(fsl_cherrypick_type_e type,
                                          fsl_uuid_cstr target,
                                          fsl_uuid_cstr baseline);
/**
   Frees a cherrypick record created by fsl_card_Q_malloc().
   Is a no-op if cp is NULL.
*/
FSL_EXPORT void fsl_card_Q_free( fsl_card_Q * cp );

/**
   Returns true (non-0) if f is not NULL and f has an opened repo
   which contains a checkin with the given rid, else it returns
   false.

   As a special case, if rid==0 then this only returns true
   if the repository currently has no content in the blob
   table.
*/
FSL_EXPORT char fsl_rid_is_a_checkin(fsl_cx * f, fsl_id_t rid);

/**
   Fetches the list of all directory names for a given checkin record
   id or (if rid is negative) the whole repo over all of its combined
   history. Each name entry in the list is appended to tgt. The
   results are reduced to unique names only and are sorted
   lexically. If addSlash is true then each entry will include a
   trailing slash character, else it will not. The list does not
   include an entry for the top-most directory.

   If rid is less than 0 then the directory list across _all_
   versions is returned. If it is 0 then the current checkout's RID
   is used (if a checkout is opened, otherwise a usage error is
   triggered). If it is positive then only directories for the
   given checkin RID are returned. If rid is specified, it is
   assumed to be the record ID of a commit (manifest) record, and
   it is impossible to distinguish between the results "invalid
   rid" and "empty directory list" (which is a legal result).

   On success it returns 0 and tgt will have a number of (char *)
   entries appended to it equal to the number of subdirectories in
   the repo (possibly 0).

   Returns non-0 on error, FSL_RC_MISUSE if !f, !tgt. On other
   errors error tgt might have been partially populated and the
   list contents should not be considered valid/complete.

   Ownership of the returned strings is transfered to the caller,
   who must eventually free each one using
   fsl_free(). fsl_list_visit_free() is the simplest way to free
   them all at once.
*/
FSL_EXPORT int fsl_repo_dir_names( fsl_cx * f, fsl_id_t rid,
                                   fsl_list * tgt, bool addSlash );


/**
   ZIPs up a copy of the contents of a specific version from f's
   opened repository db. sym is the symbolic name for the checkin
   to ZIP. filename is the name of the ZIP file to output the
   result to. See fsl_zip_writer for details and caveats of this
   library's ZIP creation. If vRootDir is not NULL and not empty
   then each file injected into the ZIP gets that directory
   prepended to its name.

   If progressVisitor is not NULL then it is called once just
   before each file is processed, passed the F-card for the file
   about to be zipped and the progressState parameter. If it
   returns non-0, ZIPping is cancelled and that error code is
   returned. This is intended primarily for providing feedback on
   the update process, but could also be used to cancel the
   operation between files.

   BUG: this function does not honor symlink content in a
   fossil-compatible fashion. If it encounters a symlink entry
   during ZIP generation, it will fail and f's error state will be
   updated with an explanation of this shortcoming.

   @see fsl_zip_writer
   @see fsl_card_F_visitor_f()
*/
FSL_EXPORT int fsl_repo_zip_sym_to_filename( fsl_cx * f, char const * sym,
                                  char const * vRootDir,
                                  char const * fileName,
                                  fsl_card_F_visitor_f progressVisitor,
                                  void * progressState);


/**
   Callback state for use with fsl_repo_extract_f() implementations
   to stream a given version of a repository's file's, one file at a
   time, to a client. Instances are never created by client code,
   only by fsl_repo_extract() and its delegates, which pass them to
   client-provided fsl_repo_extract_f() functions.
*/
struct fsl_repo_extract_state {
  /**
     The associated Fossil context.
  */
  fsl_cx * f;
  /**
     RID of the checkin version for this file. For a given call to
     fsl_repo_extract(), this number will be the same across all
     calls to the callback function.
  */
  fsl_id_t checkinRid;
  /**
     File-level blob.rid for fc. Can be used with, e.g.,
     fsl_mtime_of_manifest_file().
  */
  fsl_id_t fileRid;
  /**
     Client state passed to fsl_repo_extract(). Its interpretation
     is callback-implementation-dependent.
  */
  void * callbackState;
  /**
     The F-card being iterated over. This holds the repo-level
     metadata associated with the file, other than its RID, which is
     available via this->fileRid.

     Deleted files are NOT reported via the extraction process
     because reporting them accurately is trickier and more
     expensive than it could be. Thus this member's uuid field
     will always be non-NULL.

     Certain operations which use this class, e.g. fsl_repo_ckout()
     and fsl_ckout_update(), will temporarily synthesize an F-card to
     represent the state of a file update, in which case this object's
     contents might not 100% reflect any given db-side state. e.g.
     fsl_ckout_update() synthesizes an F-card which reflects the
     current state of a file after applying an update operation to it.
     In such cases, the fCard->uuid may refer to a repository-side
     file even though the hash of the on-disk file contents may differ
     because of, e.g., a merge.
  */
  fsl_card_F const * fCard;

  /**
     If the fsl_repo_extract_opt object which was used to initiate the
     current extraction has the extractContent member set to false,
     this will be a NULL pointer. If it's true, this member points to
     a transient buffer which holds the full, undelta'd/uncompressed
     content of fc's file record. The content bytes are owned by
     fsl_repo_extract() and are invalidated as soon as this callback
     returns, so the callback must copy/consume them immediately if
     needed.
  */
  fsl_buffer const * content;

  /**
     These counters can be used by an extraction callback to calculate
     a progress percentage.
  */
  struct {
    /** The current file number, starting at 1. */
    uint32_t fileNumber;
    /** Total number of files to extract. */
    uint32_t fileCount;
  } count;
};
typedef struct fsl_repo_extract_state fsl_repo_extract_state;

/**
   Initialized-with-defaults fsl_repo_extract_state instance, intended
   for const-copy initialization.
*/
#define fsl_repo_extract_state_empty_m {\
  NULL/*f*/, 0/*checkinRid*/, 0/*fileRid*/, \
  NULL/*state*/, NULL/*fCard*/, NULL/*content*/,    \
  {/*count*/0,0} \
}
/**
   Initialized-with-defaults fsl_repo_extract_state instance,
   intended for non-const copy initialization.
*/
FSL_EXPORT const fsl_repo_extract_state fsl_repo_extract_state_empty;

/**
   A callback type for use with fsl_repo_extract(). See
   fsl_repo_extract_state for the meanings of xstate's various
   members.  The xstate memory must be considered invalidated
   immediately after this function returns, thus implementations
   must copy or consume anything they need from xstate before
   returning.

   Implementations must return 0 on success. As a special case, if
   FSL_RC_BREAK is returned then fsl_repo_extract() will stop
   looping over files but will report it as success (by returning
   0). Any other code causes extraction looping to stop and is
   returned as-is to the caller of fsl_repo_extract().

   When returning an error, the client may use fsl_cx_err_set() to
   populate state->f with a useful error message which will
   propagate back up through the call stack.

   @see fsl_repo_extract()
*/
typedef int (*fsl_repo_extract_f)( fsl_repo_extract_state const * xstate );

/**
   Options for use with fsl_repo_extract().
*/
struct fsl_repo_extract_opt {
  /**
     The version of the repostitory to check out. This must be
     the blob.rid of a checkin artifact.
  */
  fsl_id_t checkinRid;
  /**
     The callback to call for each extracted file in the checkin.
     May not be NULL.
  */
  fsl_repo_extract_f callback;
  /**
     Optional state pointer to pass to the callback when extracting.
     Its interpretation is client-dependent.
  */
  void * callbackState;
  /**
     If true, the fsl_repo_extract_state::content pointer passed to
     the callback will be non-NULL and will contain the content of the
     file. If false, that pointer will be NULL. Such extraction is a
     relatively costly operation, so should only be enabled when
     necessary. Some uses cases can delay this decision until the
     callback and only fetch the content for cases which need it.
  */
  bool extractContent;
};

typedef struct fsl_repo_extract_opt fsl_repo_extract_opt;
/**
   Initialized-with-defaults fsl_repo_extract_opt instance, intended
   for intializing via const-copy initialization.
*/
#define fsl_repo_extract_opt_empty_m \
  {0/*checkinRid*/,NULL/*callback*/, \
   NULL/*callbackState*/,false/*extractContent*/}
/**
   Initialized-with-defaults fsl_repo_extract_opt instance,
   intended for intializing new non-const instances.
*/
FSL_EXPORT const fsl_repo_extract_opt fsl_repo_extract_opt_empty;

/**
   Extracts the contents of a single checkin from a repository,
   sending the appropriate version of each file's contents to a
   client-specified callback.

   For each file in the given checkin, opt->callback() is passed a
   fsl_repo_extract_state instance containing enough information to,
   e.g., unpack the contents to a working directory, add it to a
   compressed archive, or send it to some other destination.

   Returns 0 on success, non-0 on error. It will fail if f has no
   opened repository db.

   If the callback returns any code other than 0 or FSL_RC_BREAK,
   looping over the list of files ends and this function returns
   that value. FSL_RC_BREAK causes looping to stop but 0 is
   returned.

   Files deleted by the given version are NOT reported to the callback
   (because getting sane semantics has proven to be tricker and more
   costly than it's worth).

   See fsl_repo_extract_f() for more details about the semantics of
   the callback. See fsl_repo_extract_opt for the documentation of the
   various options.

   Fossil's internal metadata format guarantees that files will passed
   be passed to the callback in "lexical order" (as defined by
   fossil's manifest format definition). i.e. the files will be passed
   in case-sensitive, alphabetical order. Note that upper-case letters
   sort before lower-case ones.

   Sidebar: this function makes a bitwise copy of the 2nd argument
   before starting its work, just in case the caller gets the crazy
   idea to modify it from the extraction callback. Whether or not
   there are valid/interesting uses for such modification remains to
   be seen. If any are found, this copy behavior may change.
*/
FSL_EXPORT int fsl_repo_extract( fsl_cx * f,
                                 fsl_repo_extract_opt const * opt );

/**
   Equivalent to fsl_tag_rid() except that it takes a symbolic
   artifact name in place of an artifact ID as the third
   argumemnt.

   This function passes symToTag to fsl_sym_to_rid(), and on
   success passes the rest of the parameters as-is to
   fsl_tag_rid(). See that function the semantics of the other
   arguments and the return value, as well as a description of the
   side effects.
*/
FSL_EXPORT int fsl_tag_sym( fsl_cx * f, fsl_tagtype_e tagType,
                 char const * symToTag, char const * tagName,
                 char const * tagValue, char const * userName,
                 double mtime, fsl_id_t * newId );

/**
   Adds a control record to f's repositoriy that either creates or
   cancels a tag.

   artifactRidToTag is the RID of the record to be tagged.

   tagType is the type (add, cancel, or propagate) of tag.

   tagName is the name of the tag. Must not be NULL/empty.

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

   userName is the user's name to apply to the artifact. May not be
   empty/NULL. Use fsl_guess_user_name() to try to figure out a
   proper user name based on the environment. See also:
   fsl_cx_user_get(), but note that the application must first
   use fsl_cx_user_set() to set a context's user name.

   mtime is the Julian Day timestamp for the new artifact. Pass a
   value <=0 to use the current time.

   If newId is not NULL then on success the rid of the new tag control
   artifact is assigned to *newId.

   Returns 0 on success and has about a million and thirteen
   possible error conditions. On success a new artifact record is
   written to the db, its RID being written into newId as described
   above.

   If the artifact being tagged is private, the new tag is also
   marked as private.

*/
FSL_EXPORT int fsl_tag_an_rid( fsl_cx * f, fsl_tagtype_e tagType,
                 fsl_id_t artifactRidToTag, char const * tagName,
                 char const * tagValue, char const * userName,
                 double mtime, fsl_id_t * newId );

/**
    Searches for a repo.tag entry given name in the given context's
    repository db. If found, it returns the record's id. If no
    record is found and create is true (non-0) then a tag is created
    and its entry id is returned. Returns 0 if it finds no entry, a
    negative value on error. On db-level error, f's error state is
    updated.
*/
FSL_EXPORT fsl_id_t fsl_tag_id( fsl_cx * f, char const * tag, bool create );


/**
   Returns true if the checkin with the given rid is a leaf, false if
   not. Returns false if f has no repo db opened, the query fails
   (likely indicating that it is not a repository db), or just about
   any other conceivable non-success case.

   A leaf, by the way, is a commit which has no children in the same
   branch.

   Sidebar: this function calculates whether the RID is a leaf, as
   opposed to checking the "static" (pre-calculated) list of leaves in
   the [leaf] table.
*/
FSL_EXPORT bool fsl_rid_is_leaf(fsl_cx * f, fsl_id_t rid);

/**
   Counts the number of primary non-branch children for the given
   check-in.

   A primary child is one where the parent is the primary parent, not
   a merge parent.  A "leaf" is a node that has zero children of any
   kind.  This routine counts only primary children.

   A non-branch child is one which is on the same branch as the parent.

   Returns a negative value on error.
*/
FSL_EXPORT fsl_int_t fsl_count_nonbranch_children(fsl_cx * f, fsl_id_t rid);

/**
   Looks for the delta table record where rid==deltaRid, and
   returns that record's srcid via *rv. Returns 0 on success, non-0
   on error. If no record is found, *rv is set to 0 and 0 is
   returned (as opposed to FSL_RC_NOT_FOUND) because that generally
   simplifies the error checking.
*/
FSL_EXPORT int fsl_delta_src_id( fsl_cx * f, fsl_id_t deltaRid, fsl_id_t * rv );


/**
   Return true if the given artifact ID should is listed in f's
   shun table, else false.
*/
FSL_EXPORT int fsl_uuid_is_shunned(fsl_cx * f, fsl_uuid_cstr zUuid);


/**
   Compute the "mtime" of the file given whose blob.rid is "fid"
   that is part of check-in "vid".  The mtime will be the mtime on
   vid or some ancestor of vid where fid first appears. Note that
   fossil does not track the "real" mtimes of files, it only
   computes reasonable estimates for those files based on the
   timestamps of their most recent checkin in the ancestry of vid.

   On success, if pMTime is not null then the result is written to
   *pMTime.

   If fid is 0 or less then the checkin time of vid is written to
   pMTime (this is a much less expensive operation, by the way).
   In this particular case, FSL_RC_NOT_FOUND is returned if vid is
   not a valid checkin version.

   Returns 0 on success, non-0 on error. Returns FSL_RC_NOT_FOUND
   if fid is not found in vid.

   This routine is much more efficient if used to answer several
   queries in a row for the same manifest (the vid parameter). It
   is least efficient when it is passed intermixed manifest IDs,
   e.g. (1, 3, 1, 4, 1,...). This is a side-effect of the caching
   used in the computation of ancestors for a given vid.
*/
FSL_EXPORT int fsl_mtime_of_manifest_file(fsl_cx * f, fsl_id_t vid, fsl_id_t fid, fsl_time_t *pMTime);

/**
   A convenience form of fsl_mtime_of_manifest_file() which looks up
   fc's RID based on its UUID. vid must be the RID of the checkin
   version fc originates from. See fsl_mtime_of_manifest_file() for
   full details - this function simply calculates the 3rd argument
   for that one.
*/
FSL_EXPORT int fsl_mtime_of_F_card(fsl_cx * f, fsl_id_t vid, fsl_card_F const * fc, fsl_time_t *pMTime);

/**
   Ensures that the given list has capacity for at least n entries. If
   the capacity is currently equal to or less than n, this is a no-op
   unless n is 0, in which case li->list is freed and the list is
   zeroed out. Else li->list is expanded to hold at least n
   elements. Returns 0 on success, FSL_RC_OOM on allocation error.
 */
int fsl_card_F_list_reserve( fsl_card_F_list * li, uint32_t n );

/**
   Frees all memory owned by li and the F-cards it contains. Does not
   free the li pointer.
*/
void fsl_card_F_list_finalize( fsl_card_F_list * li );

/**
   Holds options for use with fsl_branch_create().
*/
struct fsl_branch_opt {
  /**
     The checkin RID from which the branch should originate.
  */
  fsl_id_t basisRid;
  /**
     The name of the branch. May not be NULL or empty.
  */
  char const * name;
  /**
     User name for the branch. If NULL, fsl_cx_user_get() will
     be used.
  */ 
  char const * user;
  /**
     Optional comment (may be NULL). If NULL or empty, a default
     comment is generated (because fossil requires a non-empty
     comment string).
  */
  char const * comment;
  /**
     Optional background color for the fossil(1) HTML timeline
     view.  Must be in \#RRGGBB format, but this API does not
     validate it as such.
  */
  char const * bgColor;
  /**
     The julian time of the branch. If 0 or less, default is the
     current time.
  */
  double mtime;
  /**
     If true, the branch will be marked as private.
  */
  char isPrivate;
};
typedef struct fsl_branch_opt fsl_branch_opt;
#define fsl_branch_opt_empty_m {                \
    0/*basisRid*/, NULL/*name*/,                \
      NULL/*user*/, NULL/*comment*/,            \
      NULL/*bgColor*/,                          \
      0.0/*mtime*/, 0/*isPrivate*/              \
      }
FSL_EXPORT const fsl_branch_opt fsl_branch_opt_empty;

/**
   Creates a new branch in f's repository. The 2nd paramter holds
   the options describing the branch. The 3rd parameter may be
   NULL, but if it is not then on success the RID of the new
   manifest is assigned to *newRid.

   In Fossil branches are implemented as tags. The branch name
   provided by the client will cause the creation of a tag with
   name name plus a "sym-" prefix to be created (if needed).
   "sym-" denotes that it is a "symbolic tag" (fossil's term for
   "symbolic name applying to one or more checkins,"
   i.e. branches).

   Creating a branch cancels all other branch tags which the new
   branch would normally inherit.

   Returns 0 on success, non-0 on error. 
*/
FSL_EXPORT int fsl_branch_create(fsl_cx * f, fsl_branch_opt const * opt, fsl_id_t * newRid );


/**
   Tries to determine the [filename.fnid] value for the given
   filename.  Returns a positive value if it finds one, 0 if it
   finds none, and some unspecified negative value(s) for any sort
   of error. filename must be a normalized, relative filename (as it
   is recorded by a repo).
*/
FSL_EXPORT fsl_id_t fsl_repo_filename_fnid( fsl_cx * f, char const * filename );


/**
   Imports content to f's opened repository's BLOB table using a
   client-provided input source. f must have an opened repository
   db. inFunc is the source of the data and inState is the first
   argument passed to inFunc(). If inFunc() succeeds in fetching all
   data (i.e. if it always returns 0 when called by this function)
   then that data is inserted into the blob table _if_ no existing
   record with the same hash is already in the table. If such a record
   exists, it is assumed that the content is identical and this
   function has no side-effects vis-a-vis the db in that case.

   If rid is not NULL then the BLOB.RID record value (possibly of an
   older record!) is stored in *rid.  If uuid is not NULL then the
   BLOB.UUID record value is stored in *uuid and the caller takes
   ownership of those bytes, which must eventually be passed to
   fsl_free() to release them.

   rid and uuid are only modified on success and only if they are
   not NULL.

   Returns 0 on success, non-0 on error. For errors other than basic
   argument validation and OOM conditions, f's error state is
   updated with a description of the problem. Returns FSL_RC_MISUSE
   if either f or inFunc are NULL. Whether or not inState may be
   NULL depends on inFunc's concrete implementation.

   Be aware that BLOB.RID values can (but do not necessarily) change
   in the life of a repod db (via a reconstruct, a full re-clone, or
   similar, or simply when referring to different clones of the same
   repo). Thus clients should always store the full UUID, as opposed
   to the RID, for later reference. RIDs should, in general, be
   treated as session-transient values. That said, for purposes of
   linking tables in the db, the RID is used exclusively (clients are
   free to link their own extension tables using UUIDs, but doing so
   has a performance penalty comared to RIDs). For long-term storage
   of external links, and to guaranty that the data be usable with
   other copies of the same repo, the UUID is required.

   Note that Fossil may deltify, compress, or otherwise modify
   content on its way into the blob table, and it may even modify
   content long after its insertion (e.g. to make it a delta against
   a newer version). Thus clients should normally never try
   to read back the blob directly from the database, but should
   instead read it using fsl_content_get().

   That said: this routine has no way of associating and older version
   (if any) of the same content with this newly-imported version, and
   therefore cannot delta-compress the older version.

   Maintenance reminder: this is basically just a glorified form of
   the internal fsl_content_put(). Interestingly, fsl_content_put()
   always sets content to public (by default - the f object may
   override that later). It is not yet clear whether this routine
   needs to have a flag to set the blob private or not. Generally
   speaking, privacy is applied to fossil artifacts, as opposed to
   content blobs.

   @see fsl_repo_import_buffer()
*/
FSL_EXPORT int fsl_repo_import_blob( fsl_cx * f, fsl_input_f inFunc,
                                     void * inState, fsl_id_t * rid,
                                     fsl_uuid_str * uuid );

/**
   A convenience form of fsl_repo_import_blob(), equivalent to:

   @code
   fsl_repo_import_blob(f, fsl_input_f_buffer, bIn, rid, uuid )
   @endcode

   except that (A) bIn is const in this call and non-const in the
   other form (due to cursor traversal requirements) and (B) it
   returns FSL_RC_MISUSE if bIn is NULL.
*/
FSL_EXPORT int fsl_repo_import_buffer( fsl_cx * f, fsl_buffer const * bIn,
                                       fsl_id_t * rid, fsl_uuid_str * uuid );

/**
   Resolves client-provided symbol as an artifact's db record ID.
   f must have an opened repository db, and some symbols can only
   be looked up if it has an opened checkout (see the list below).

   Returns 0 and sets *rv to the id if it finds an unambiguous
   match.

   Returns FSL_RC_MISUSE if !f, !sym, !*sym, or !rv.

   Returns FSL_RC_NOT_A_REPO if f has no opened repository.

   Returns FSL_RC_AMBIGUOUS if sym is a partial UUID which matches
   multiple full UUIDs.

   Returns FSL_RC_NOT_FOUND if it cannot find anything.

   Symbols supported by this function:

   - SHA1/3 hash
   - SHA1/3 hash prefix of at least 4 characters
   - Symbolic Name
   - "tag:" + symbolic name
   - Date or date-time 
   - "date:" + Date or date-time
   - symbolic-name ":" date-time
   - "tip"

   - "rid:###" resolves to the hash of blob.rid ### if that RID is in
   the database

   The following additional forms are available in local checkouts:

   - "current"
   - "prev" or "previous"
   - "next"

   The following prefix may be applied to the above to modify how
   they are resolved:

   - "root:" prefix resolves to the checkin of the parent branch from
   which the record's branch divered. i.e. the version from which it
   was branched. In the trunk this will always resolve to the first
   checkin.

   - "merge-in:" TODO - document this once its implications are
   understood.

   If type is not FSL_SATYPE_ANY then it will only match artifacts
   of the specified type. In order to resolve arbitrary UUIDs, e.g.
   those of arbitrary blob content, type needs to be
   FSL_SATYPE_ANY.

*/
FSL_EXPORT int fsl_sym_to_rid( fsl_cx * f, char const * sym, fsl_satype_e type,
                               fsl_id_t * rv );

/**
   Similar to fsl_sym_to_rid() but on success it returns a UUID string
   by assigning it to *rv (if rv is not NULL). If rid is not NULL then
   on success the db record ID corresponding to the returned UUID is
   assigned to *rid. The caller must eventually free the returned
   string memory by passing it to fsl_free(). Returns 0 if it finds a
   match and any number of result codes on error.
*/
FSL_EXPORT int fsl_sym_to_uuid( fsl_cx * f, char const * sym,
                                fsl_satype_e type, fsl_uuid_str * rv,
                                fsl_id_t * rid );


/**
   Searches f's repo database for the a blob with the given uuid
   (any unique UUID prefix). On success a positive record ID is
   returned. On error one of several unspecified negative values is
   returned. If no uuid match is found 0 is returned.

   Error cases include: either argument is NULL, uuid does not
   appear to be a full or partial UUID (or is too long),
   uuid is ambiguous (try providing a longer one)

   This implementation is more efficient when given a full,
   valid UUID (one for which fsl_is_uuid() returns true).
*/
FSL_EXPORT fsl_id_t fsl_uuid_to_rid( fsl_cx * f, char const * uuid );

/**
   The opposite of fsl_uuid_to_rid(), this returns the UUID string
   of the given blob record ID. Ownership of the string is passed
   to the caller and it must eventually be freed using
   fsl_free(). Returns NULL on error (invalid arguments or f has no
   repo opened) or if no blob record is found. If no record is
   found, f's error state is updated with an explanation of the
   problem.
*/
FSL_EXPORT fsl_uuid_str fsl_rid_to_uuid(fsl_cx * f, fsl_id_t rid);

/**
   Works like fsl_rid_to_uuid() but assigns the UUID to the given
   buffer, re-using its memory, if any. Returns 0 on success,
   FSL_RC_MISUSE if rid is not positive, FSL_RC_OOM on allocation
   error, and FSL_RC_NOT_FOUND if no blob entry matching the given rid
   is found.
*/
FSL_EXPORT int fsl_rid_to_uuid2(fsl_cx * f, fsl_id_t rid, fsl_buffer *uuid);

/**
   This works identically to fsl_rid_to_uuid() except that it will
   only resolve to a UUID if an artifact matching the given type has
   that UUID. If no entry is found, f's error state gets updated
   with a description of the problem.

   This can be used to distinguish artifact UUIDs from file blob
   content UUIDs by passing the type FSL_SATYPE_ANY. A non-artifact
   blob will return NULL in that case, but any artifact type will
   match (assuming rid is valid).
*/
FSL_EXPORT fsl_uuid_str fsl_rid_to_artifact_uuid(fsl_cx * f, fsl_id_t rid,
                                                 fsl_satype_e type);
/**
   Returns the raw SQL code for a Fossil global config database.

   TODO: add optional (fsl_size_t*) to return the length.
*/
FSL_EXPORT char const * fsl_schema_config();

/**
   Returns the raw SQL code for the "static" parts of a Fossil
   repository database. These are the parts which are immutable
   (for the most part) between Fossil versions. They change _very_
   rarely.

   TODO: add optional (fsl_size_t*) to return the length.
*/
FSL_EXPORT char const * fsl_schema_repo1();

/**
   Returns the raw SQL code for the "transient" parts of a Fossil
   repository database - any parts which can be calculated via data
   held in the primary "static" schemas. These parts are
   occassionally recreated, e.g. via a 'rebuild' of a repository.

   TODO: add optional (fsl_size_t*) to return the length.
*/
FSL_EXPORT char const * fsl_schema_repo2();

/**
   Returns the raw SQL code for a Fossil checkout database.

   TODO: add optional (fsl_size_t*) to return the length.
*/
FSL_EXPORT char const * fsl_schema_ckout();

/**
   Returns the raw SQL code for a Fossil checkout db's
   _default_ core ticket-related tables.

   TODO: add optional (fsl_size_t*) to return the length.

   @see fsl_cx_schema_ticket()
*/
FSL_EXPORT char const * fsl_schema_ticket();

/**
   Returns the raw SQL code for the "forum" parts of a Fossil
   repository database.

   TODO: add optional (fsl_size_t*) to return the length.
*/
FSL_EXPORT char const * fsl_schema_forum();

/**
   If f's opened repository has a non-empty config entry named
   'ticket-table', this returns its text via appending it to
   pOut. If no entry is found, fsl_schema_ticket() is appended to
   pOut.

   Returns 0 on success. On error the contents of pOut must not be
   considered valid but pOut might be partially populated.
*/
FSL_EXPORT int fsl_cx_schema_ticket(fsl_cx * f, fsl_buffer * pOut);

/**
   Returns the raw SQL code for Fossil ticket reports schemas.
   This gets installed as needed into repository databases.

   TODO: add optional (fsl_size_t*) to return the length.
*/
FSL_EXPORT char const * fsl_schema_ticket_reports();

/**
   This is a wrapper around fsl_cx_hash_buffer() which looks for a
   matching artifact for the given input blob. It first hashes src
   using f's "alternate" hash and then, if no match is found, tries
   again with f's preferred hash.

   On success (a match is found):

   - Returns 0.

   - If ridOut is not NULL, *ridOut is set to the RID of the matching blob.

   - If hashOut is not NULL, *hashOut is set to the hash of the
   blob. Its ownership is transferred to the caller, who must
   eventually pass it to fsl_free().

   If no matching blob is found in the repository, FSL_RC_NOT_FOUND is
   returned (but f's error state is not annotated with more
   information). Returns FSL_RC_NOT_A_REPO if f has no repository
   opened. For more serious errors, e.g. allocation error or db
   problems, another (more serious) result code is returned,
   e.g. FSL_RC_OOM or FSL_RC_DB.

   If FSL_RC_NOT_FOUND is returned and hashOut is not NULL, *hashOut
   is set to the value of f's preferred hash. *ridOut is only modified
   if 0 is returned, in which case *ridOut will have a positive value.
*/
FSL_EXPORT int fsl_repo_blob_lookup( fsl_cx * f, fsl_buffer const * src, fsl_id_t * ridOut,
                                     fsl_uuid_str * hashOut );

/**
   Returns true if the specified file name ends with any reserved
   name, e.g.: _FOSSIL_ or .fslckout.

   For the sake of efficiency, zFilename must be a canonical name,
   e.g. an absolute or checkout-relative path using only forward slash
   ('/') as a directory separator.

   On Windows builds, this also checks for reserved Windows filenames,
   e.g. "CON" and "PRN".

   nameLen must be the length of zFilename. If it is negative,
   fsl_strlen() is used to calculate it.
*/
FSL_EXPORT bool fsl_is_reserved_fn(const char *zFilename,
                                   fsl_int_t nameLen );

/**
   Uses fsl_is_reserved_fn() to determine whether the filename part of
   zPath is legal for use as an in-repository filename. If it is, 0 is
   returned, else FSL_RC_RANGE (or FSL_RC_OOM) is returned and f's
   error state is updated to indicate the nature of the problem. nFile
   is the length of zPath. If negative, fsl_strlen() is used to
   determine its length.

   If relativeToCwd is true then zPath, if not absolute, is
   canonicalized as if were relative to the current working directory
   (see fsl_getcwd()), else it is assumed to be relative to the
   current checkout (if any - falling back to the current working
   directory). This flag is only relevant if zPath is not absolute and
   if f has a checkout opened. An absolute zPath is used as-is and if
   no checkout is opened then relativeToCwd is always treated as if it
   were true.

   This routine does not validate that zPath lives inside a checkout
   nor that the file actually exists. It does only name comparison and
   only uses the filesystem for purposes of canonicalizing (if needed)
   zPath.

   This routine does not require that f have an opened repo, but if it
   does then this routine compares the canonicalized forms of both the
   repository db and the given path and fails if zPath refers to the
   repository db. Be aware that the relativeToCwd flag may influence
   that test.

   TODO/FIXME: if f's 'manifest' config setting is set to true AND
   zPath refers to the top of the checkout root, treat the files
   (manifest, manifest.uuid, manifest.tags) as reserved. If it is a
   string with any of the letters "r", "u", or "t", check only the
   file(s) which those letters represent (see
   add.c:fossil_reserved_name() in fossil). Apply these only at the top
   of the tree - allow them in subdirectories.
*/
FSL_EXPORT int fsl_reserved_fn_check(fsl_cx *f, const char *zPath,
                                     fsl_int_t nFile, bool relativeToCwd);

/**
   Recompute/rebuild the entire repo.leaf table. This is not normally
   needed, as leaf tracking is part of the crosslinking process, but
   "just in case," here it is.

   This can supposedly be expensive (in time) for a really large
   repository. Testing implies otherwise.

   Returns 0 on success. Error may indicate that f has no repo db
   opened.  On error f's error state may be updated.
*/
FSL_EXPORT int fsl_repo_leaves_rebuild(fsl_cx * f);  

/**
   Flags for use with fsl_leaves_compute().
*/
enum fsl_leaves_compute_e {
/**
   Compute all leaves regardless of the "closed" tag.
*/
FSL_LEAVES_COMPUTE_ALL = 0,
/**
   Compute only leaves without the "closed" tag.
*/
FSL_LEAVES_COMPUTE_OPEN = 1,
/**
   Compute only leaves with the "closed" tag.
*/
FSL_LEAVES_COMPUTE_CLOSED = 2
};
typedef enum fsl_leaves_compute_e fsl_leaves_compute_e;

/**
   Creates a temporary table named "leaves" if it does not already
   exist, else empties it. Populates that table with the RID of all
   check-ins that are leaves which are descended from the checkin
   referred to by vid.

   A "leaf" is a check-in that has no children in the same branch.
   There is a separate permanent table named [leaf] that contains all
   leaves in the tree. This routine is used to compute a subset of
   that table consisting of leaves that are descended from a single
   check-in.
   
   The leafMode flag determines behavior associated with the "closed"
   tag, as documented for the fsl_leaves_compute_e enum.

   If vid is <=0 then this function, after setting up or cleaning out
   the [leaves] table, simply copies the list of leaves from the
   repository's pre-computed [leaf] table (see
   fsl_repo_leaves_rebuild()).

   @see fsl_leaves_computed_has()
   @see fsl_leaves_computed_count()
   @see fsl_leaves_computed_latest()
   @see fsl_leaves_computed_cleanup()
*/
FSL_EXPORT int fsl_leaves_compute(fsl_cx * f, fsl_id_t vid,
                                  fsl_leaves_compute_e leafMode);

/**
   Requires that a prior call to fsl_leaves_compute() has succeeded,
   else results are undefined.

   Returns true if the leaves list computed by fsl_leaves_compute() is
   not empty, else false. This is more efficient than checking
   against fsl_leaves_computed_count()>0.
*/
FSL_EXPORT bool fsl_leaves_computed_has(fsl_cx * f);

/**
   Requires that a prior call to fsl_leaves_compute() has succeeded,
   else results are undefined.

   Returns a count of the leaves list computed by
   fsl_leaves_compute(), or a negative value if a db-level error is
   encountered. On errors other than FSL_RC_OOM, f's error state will
   be updated with information about the error.
*/
FSL_EXPORT fsl_int_t fsl_leaves_computed_count(fsl_cx * f);

/**
   Requires that a prior call to fsl_leaves_compute() has succeeded,
   else results are undefined.

   Returns the RID of the most recent checkin from those computed by
   fsl_leaves_compute(), 0 if no entries are found, or a negative
   value if a db-level error is encountered. On errors other than
   FSL_RC_OOM, f's error state will be updated with information about
   the error.
*/
FSL_EXPORT fsl_id_t fsl_leaves_computed_latest(fsl_cx * f);

/**
   Cleans up any db-side resources created by fsl_leaves_compute().
   e.g. drops the temporary table created by that routine. Any errors
   are silenty ignored.
*/
FSL_EXPORT void fsl_leaves_computed_cleanup(fsl_cx * f);

/**
   Returns true if f's current repository has the
   forbid-delta-manifests setting set to a truthy value. Results are
   undefined if f has no opened repository. Some routines behave
   differently if this setting is enabled. e.g. fsl_checkin_commit()
   will never generate a delta manifest and fsl_deck_save() will
   refuse to save a delta. This does not affect parsing or deltas or
   those which are injected into the db via lower-level means (e.g. a
   direct blob import or from a remote sync).

   Results are undefined if f has no opened repository.
*/
FSL_EXPORT bool fsl_repo_forbids_delta_manifests(fsl_cx * f);

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












































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted include/fossil-scm/fossil-util.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
4004
4005
4006
4007
4008
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
4021
4022
4023
4024
4025
4026
4027
4028
4029
4030
4031
4032
4033
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151
4152
4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235
4236
4237
4238
4239
4240
4241
4242
4243
4244
4245
4246
4247
4248
4249
4250
4251
4252
4253
4254
4255
4256
4257
4258
4259
4260
4261
4262
4263
4264
4265
4266
4267
4268
4269
4270
4271
4272
4273
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
4288
4289
4290
4291
4292
4293
4294
4295
4296
4297
4298
4299
4300
4301
4302
4303
4304
4305
4306
4307
4308
4309
4310
4311
4312
4313
4314
4315
4316
4317
4318
4319
4320
4321
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331
4332
4333
4334
4335
4336
4337
4338
4339
4340
4341
4342
4343
4344
4345
4346
4347
4348
4349
4350
4351
4352
4353
4354
4355
4356
4357
4358
4359
4360
4361
4362
4363
4364
4365
4366
4367
4368
4369
4370
4371
4372
4373
4374
4375
4376
4377
4378
4379
4380
4381
4382
4383
4384
4385
4386
4387
4388
4389
4390
4391
4392
4393
4394
4395
4396
4397
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
4417
4418
4419
4420
4421
4422
4423
4424
4425
4426
4427
4428
4429
4430
4431
4432
4433
4434
4435
4436
4437
4438
4439
4440
4441
4442
4443
4444
4445
4446
4447
4448
4449
4450
4451
4452
4453
4454
4455
4456
4457
4458
4459
4460
4461
4462
4463
4464
4465
4466
4467
4468
4469
4470
4471
4472
4473
4474
4475
4476
4477
4478
4479
4480
4481
4482
4483
4484
4485
4486
4487
4488
4489
4490
4491
4492
4493
4494
4495
4496
4497
4498
4499
4500
4501
4502
4503
4504
4505
4506
4507
4508
4509
4510
4511
4512
4513
4514
4515
4516
4517
4518
4519
4520
4521
4522
4523
4524
4525
4526
4527
4528
4529
4530
4531
4532
4533
4534
4535
4536
4537
4538
4539
4540
4541
4542
4543
4544
4545
4546
4547
4548
4549
4550
4551
4552
4553
4554
4555
4556
4557
4558
4559
4560
4561
4562
4563
4564
4565
4566
4567
4568
4569
4570
4571
4572
4573
4574
4575
4576
4577
4578
4579
4580
4581
/* -*- 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_UTIL_H_INCLUDED)
#define ORG_FOSSIL_SCM_FSL_UTIL_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).

*/

/** @file fossil-util.h

    This file declares a number of utility classes and routines used by
    libfossil. All of them considered "public", suitable for direct use
    by client code.
*/

#include "fossil-config.h" /* MUST come first b/c of config macros */
#include <stdio.h> /* FILE type */
#include <stdarg.h> /* va_list */
#include <time.h> /* tm struct */
#include <stdbool.h>
#if defined(__cplusplus)
extern "C" {
#endif
typedef struct fsl_allocator fsl_allocator;
typedef struct fsl_buffer fsl_buffer;
typedef struct fsl_error fsl_error;
typedef struct fsl_finalizer fsl_finalizer;
typedef struct fsl_fstat fsl_fstat;
typedef struct fsl_list fsl_list;
typedef struct fsl_outputer fsl_outputer;
typedef struct fsl_state fsl_state;
typedef struct fsl_id_bag fsl_id_bag;

/**
   fsl_uuid_str and fsl_uuid_cstr are "for documentation and
   readability purposes" typedefs used to denote strings which the API
   requires to be in the form of Fossil UUID strings. Such strings are
   exactly FSL_STRLEN_SHA1 or FSL_STRLEN_K256 bytes long plus a
   terminating NUL byte and contain only lower-case hexadecimal
   bytes. Where this typedef is used, the library requires, enforces,
   and/or assumes (at different times) that fsl_is_uuid() returns true
   for such strings (if they are not NULL, though not all contexts
   allow a NULL UUID). These typedef are _not_ used to denote
   arguments which may refer to partial UUIDs or symbolic names, only
   100% bonafide Fossil UUIDs (which are different from RFC4122
   UUIDs).

   The API guarantees that this typedef will always be (char *) and
   that fsl_uuid_cstr will always ben (char const *), and thus it
   is safe/portable to use those type instead of these. These
   typedefs serve only to improve the readability of certain APIs
   by implying (through the use of this typedef) the preconditions
   defined for UUID strings.

   Sidebar: fossil historically used the term UUID for blob IDs, and
   still uses that term in the DB schema, but it has fallen out of
   favor in documentation and discussions, with "hash" being the
   preferred term. Much of the libfossil code was developed before
   that happened, though, so "UUID" is still prevalent in its API and
   documentation.

   @see fsl_is_uuid()
   @see fsl_uuid_cstr
*/
typedef char * fsl_uuid_str;

/**
   The const counterpart of fsl_uuid_str.

   @see fsl_is_uuid()
   @see fsl_uuid_str
*/
typedef char const * fsl_uuid_cstr;

/**
   A typedef for comparison function used by standard C
   routines such as qsort(). It is provided here primarily
   to simplify documentation of other APIs. Concrete
   implementations must compare lhs and rhs, returning negative,
   0, or right depending on whether lhs is less than, equal to,
   or greater than rhs.

   Implementations might need to be able to deal with NULL
   arguments. That depends on the routine which uses the comparison
   function.
*/
typedef int (*fsl_generic_cmp_f)( void const * lhs, void const * rhs );

/**
   If the NUL-terminated input str is exactly FSL_STRLEN_SHA1 or
   FSL_STRLEN_K256 bytes long and contains only lower-case
   hexadecimal characters, returns the length of the string, else
   returns 0.

   Note that Fossil UUIDs are not RFC4122 UUIDs, but are SHA1 or
   SHA3-256 hash strings. Don't let that disturb you. As Tim
   Berners-Lee writes:

   'The assertion that the space of URIs is a universal space
   sometimes encounters opposition from those who feel there should
   not be one universal space. These people need not oppose the
   concept because it is not of a single universal space: Indeed,
   the fact that URIs form universal space does not prevent anyone
   else from forming their own universal space, which of course by
   definition would be able to envelop within it as a subset the
   universal URI space. Therefore the web meets the "independent
   design" test, that if a similar system had been concurrently and
   independently invented elsewhere, in such a way that the
   arbitrary design decisions were made differently, when they met
   later, the two systems could be made to interoperate.'

   Source: https://www.w3.org/DesignIssues/Axioms.html

   (Just mentally translate URI as UUID.)
*/
FSL_EXPORT int fsl_is_uuid(char const * str);

/**
   If x is a valid fossil UUID length, it is returned, else 0 is returned.
*/
FSL_EXPORT int fsl_is_uuid_len(int x);

/**
   Expects str to be a string containing an unsigned decimal
   value. Returns its decoded value, or -1 on error.
*/
FSL_EXPORT fsl_size_t fsl_str_to_size(char const * str);

/**
   Expects str to be a string containing a decimal value,
   optionally with a leading sign. Returns its decoded value, or
   dflt if !str or on error.
*/
FSL_EXPORT fsl_int_t fsl_str_to_int(char const * str, fsl_int_t dflt);


/**
   Generic list container type. This is used heavily by the Fossil
   API for storing arrays of dynamically-allocated objects. It is
   not useful as a non-pointer-array replacement.

   It is up to the APIs using this type to manage the entry count
   member and use fsl_list_reserve() to manage the "capacity"
   member.

   @see fsl_list_reserve()
   @see fsl_list_append()
   @see fsl_list_visit()
*/
struct fsl_list {
  /**
     Array of entries. It contains this->capacity entries,
     this->count of which are "valid" (in use).
  */
  void ** list;
  /**
     Number of "used" entries in the list.
  */
  fsl_size_t used;
  /**
     Number of slots allocated in this->list. Use fsl_list_reserve()
     to modify this. Doing so might move the this->list pointer but
     the values it points to will stay stable.
  */
  fsl_size_t capacity;
};

/**
   Empty-initialized fsl_list structure, intended for const-copy
   initialization.
*/
#define fsl_list_empty_m { NULL, 0, 0 }
/**
   Empty-initialized fsl_list structure, intended for copy
   initialization.
*/
FSL_EXPORT const fsl_list fsl_list_empty;


/**
   Generic interface for finalizing/freeing memory. Intended
   primarily for use as a destructor/finalizer for high-level
   structs. Implementations must semantically behave like free(mem),
   regardless of whether or not they actually free the memory. At
   the very least, they generally should clean up any memory owned by
   mem (e.g. db resources or buffers), even if they do not free() mem.
   some implementations assume that mem is stack-allocated
   and they only clean up resources owned by mem.

   The state parameter is any state needed by the finalizer
   (e.g. a memory allocation context) and mem is the memory which is
   being finalized. 

   The exact interpretaion of the state and mem are of course
   implementation-specific.
*/
typedef void (*fsl_finalizer_f)( void * state, void * mem );

/**
   Generic interface for memory finalizers.
*/
struct fsl_finalizer {
  /**
     State to be passed as the first argument to f().
  */
  void * state;
  /**
     Finalizer function. Should be called like this->f( this->state, ... ).
  */
  fsl_finalizer_f f;
};

/** Empty-initialized fsl_finalizer struct. */
#define fsl_finalizer_empty_m {NULL,NULL}

/**
   fsl_finalizer_f() impl which requires that mem be-a
   (fsl_buffer*).  This function frees all memory associated with
   that buffer and zeroes out the structure, but does not free mem
   (because it is rare that fsl_buffers are created on the
   heap). The state parameter is ignored.
*/
FSL_EXPORT int fsl_finalizer_f_buffer( void * state, void * mem );


/**
   Generic state-with-finalizer holder. Used for binding
   client-specified state to another object, such that a
   client-specified finalizer is called with the other object is
   cleaned up.
*/
struct fsl_state {
  /**
     Arbitrary context-dependent state.
  */
  void * state;
  /**
     Finalizer for this->state. If used, it should be called like:

     @code
     this->finalize.f( this->finalize.state, this->state );
     @endcode

     After which this->state must be treated as if it has been
     free(3)'d.
  */
  fsl_finalizer finalize;
};

/** Empty-initialized fsl_state struct. */
#define fsl_state_empty_m {NULL,fsl_finalizer_empty_m}

/**
   Empty-initialized fsl_state struct, intended for
   copy-initializing.
*/
FSL_EXPORT const fsl_state fsl_state_empty;


/**
   Generic interface for streaming out data. Implementations must
   write n bytes from s to their destination channel and return 0 on
   success, non-0 on error (assumed to be a value from the fsl_rc_e
   enum). The state parameter is the implementation-specified
   output channel.

   Potential TODO: change the final argument to a pointer, with
   semantics similar to fsl_input_f(): at call-time n is the number
   of bytes to output, and on returning n is the number of bytes
   actually written. This would allow, e.g. the fsl_zip_writer APIs
   to be able to stream a ZIP file (they have to know the real size
   of the output, and this interface doesn't support that
   operation).
*/
typedef int (*fsl_output_f)( void * state,
                             void const * src, fsl_size_t n );


/**
   Generic interface for flushing arbitrary output streams.  Must
   return 0 on success, non-0 on error, but the result code
   "should" (to avoid downstream confusion) be one of the fsl_rc_e
   values. When in doubt, return FSL_RC_IO on error. The
   interpretation of the state parameter is
   implementation-specific.
*/
typedef int (*fsl_flush_f)(void * state);

/**
   Generic interface for streaming in data. Implementations must
   read (at most) *n bytes from their input, copy it to dest, assign
   *n to the number of bytes actually read, return 0 on success, and
   return non-0 on error (assumed to be a value from the fsl_rc_e
   enum). When called, *n is the max length to read. On return, *n
   is the actual amount read. The state parameter is the
   implementation-specified input file/buffer/whatever channel.
*/
typedef int (*fsl_input_f)( void * state, void * dest, fsl_size_t * n );

/**
   fsl_output_f() implementation which requires state to be a
   writeable (FILE*) handle. Is a no-op (returning 0) if
   !n. Returns FSL_RC_MISUSE if !state or !src.
*/
FSL_EXPORT int fsl_output_f_FILE( void * state, void const * src, fsl_size_t n );

/**
   fsl_output_f() implementation which requires state to be a
   (fsl_cx*) to which this routine simply redirects the output via
   fsl_output().  Is a no-op (returning 0) if !n. Returns
   FSL_RC_MISUSE if !state or !src.
*/
FSL_EXPORT int fsl_output_f_fsl_cx(void * state, void const * src, fsl_size_t n );


/**
   An interface which encapsulates data for managing an output
   destination, primarily intended for use with fsl_output(). Why
   abstract it to this level? So that we can do interesting things
   like output to buffers, files, sockets, etc., using the core
   output mechanism. e.g. so script bindings can send their output
   to the same channel used by the library and other library
   clients.
*/
struct fsl_outputer {
  /**
     Output channel.
  */
  fsl_output_f out;
  /**
     flush() implementation.
  */
  fsl_flush_f flush;
  /**
     State to be used when calling this->out(), namely:
     this->out( this->state.state, ... ).
  */
  fsl_state state;
};
/** Empty-initialized fsl_outputer instance. */
#define fsl_outputer_empty_m {NULL,NULL,fsl_state_empty_m}
/**
   Empty-initialized fsl_outputer instance, intended for
   copy-initializing.
*/
FSL_EXPORT const fsl_outputer fsl_outputer_empty;

/**
   A fsl_outputer instance which is initialized to output to a
   (FILE*). To use it, this value then set the copy's state.state
   member to an opened-for-write (FILE*) handle. By default it will
   use stdout. Its finalizer (if called!) will fclose(3)
   self.state.state if self.state.state is not one of (stdout,
   stderr). To disable the closing behaviour (and not close the
   file), set self.state.finalize.f to NULL (but then be sure that
   the file handle outlives this object and to fclose(3) it when
   finished with it).
*/
FSL_EXPORT const fsl_outputer fsl_outputer_FILE;

/**
   fsl_outputer initializer which uses fsl_flush_f_FILE(),
   fsl_output_f_FILE(), and fsl_finalizer_f_FILE().
*/
#define fsl_outputer_FILE_m {                   \
    fsl_output_f_FILE,                          \
      fsl_flush_f_FILE,                         \
      {/*state*/                                \
        NULL,                                   \
        {NULL,fsl_finalizer_f_FILE}             \
      }                                         \
  }
/**
   Generic stateful alloc/free/realloc() interface.

   Implementations must behave as follows:

   - If 0==n then semantically behave like free(3) and return
   NULL.

   - If 0!=n and !mem then semantically behave like malloc(3), returning
   newly-allocated memory on success and NULL on error.

   - If 0!=n and NULL!=mem then semantically behave like
   realloc(3). Note that realloc specifies: "If n was equal to 0,
   either NULL or a pointer suitable to be passed to free() is
   returned." Which is kind of useless, and thus implementations
   MUST return NULL when n==0.
*/
typedef void *(*fsl_realloc_f)(void * state, void * mem, fsl_size_t n);

/**
   Holds an allocator function and its related state.
*/
struct fsl_allocator {
  /**
     Base allocator function. It must be passed this->state
     as its first parameter.
  */
  fsl_realloc_f f;
  /**
     State intended to be passed as the first parameter to
     this->f().
  */
  void * state;
};

/** Empty-initialized fsl_allocator instance. */
#define fsl_allocator_empty_m {NULL,NULL}


/**
   A fsl_realloc_f() implementation which uses the standard
   malloc()/free()/realloc(). The state parameter is ignored.
*/
FSL_EXPORT void * fsl_realloc_f_stdalloc(void * state, void * mem, fsl_size_t n);


/**
   Semantically behaves like malloc(3), but may introduce instrumentation,
   error checking, or similar.
*/
void * fsl_malloc( fsl_size_t n )
#ifdef __GNUC__
  __attribute__ ((malloc))
#endif
  ;

/**
   Semantically behaves like free(3), but may introduce instrumentation,
   error checking, or similar.
*/
FSL_EXPORT void fsl_free( void * mem );

/**
   Behaves like realloc(3). Clarifications on the behaviour (because
   the standard has one case of unfortunate wording involving what
   it returns when n==0):

   - If passed (NULL, n>0) then it semantically behaves like
   fsl_malloc(f, n).

   - If 0==n then it semantically behaves like free(2) and returns
   NULL (clarifying the aforementioned wording problem).

   - If passed (non-NULL, n) then it semantically behaves like
   realloc(mem,n).

*/
FSL_EXPORT void * fsl_realloc( void * mem, fsl_size_t n );

/**
   A fsl_flush_f() impl which expects _FILE to be-a (FILE*) opened
   for writing, which this function passes the call on to
   fflush(). If fflush() returns 0, so does this function, else it
   returns non-0.
*/
FSL_EXPORT int fsl_flush_f_FILE(void * _FILE);

/**
   A fsl_finalizer_f() impl which requires that mem be-a (FILE*).
   This function passes that FILE to fsl_fclose(). The state
   parameter is ignored.
*/
FSL_EXPORT void fsl_finalizer_f_FILE( void * state, void * mem );

/**
   A fsl_output_f() impl which requires state to be-a (FILE*), which
   this function passes the call on to fwrite(). Returns 0 on
   success, FSL_RC_IO on error.
*/
FSL_EXPORT int fsl_output_f_FILE( void * state, void const * src, fsl_size_t n );

/**
   A fsl_output_f() impl which requires state to be-a (fsl_buffer*),
   which this function passes to fsl_buffer_append(). Returns 0 on
   success, FSL_RC_OOM (probably) on error.
*/
FSL_EXPORT int fsl_output_f_buffer( void * state, void const * src, fsl_size_t n );

/**
   A fsl_input_f() implementation which requires that state be
   a readable (FILE*) handle.
*/
FSL_EXPORT int fsl_input_f_FILE( void * state, void * dest, fsl_size_t * n );

/**
   A fsl_input_f() implementation which requires that state be a
   readable (fsl_buffer*) handle. The buffer's cursor member is
   updated to track input postion, but that is the only
   modification made by this routine. Thus the user may need to
   reset the cursor to 0 if he wishes to start consuming the buffer
   at its starting point. Subsequent calls to this function will
   increment the cursor by the number of bytes returned via *n.
   The buffer's "used" member is used to determine the logical end
   of input.

   Returns 0 on success and has no error conditions except for
   invalid arguments, which result in undefined beavhiour. Results
   are undefined if any argument is NULL.

   Tip (and warning): sometimes a routine might have a const buffer
   handle which it would like to use in conjunction with this
   routine but cannot withou violating constness. Here's a crude
   workaround:

   @code
   fsl_buffer kludge = *originalConstBuffer; // normally this is dangerous!
   rc = some_func( fsl_input_f_buffer, &kludge, ... );
   assert(kludge.mem==originalConstBuffer->mem); // See notes below.
   // DO NOT clean up the kludge buffer. Memory belongs to the original!
   @endcode

   That is ONLY (ONLY! ONLY!! ONLY!!!) legal because this routine
   modifies only fsl_buffer::cursor. Such a workaround is STRICLY
   ILLEGAL if there is ANY CHANCE WHATSOEVER that the buffer's
   memory will be modified, in particular if it will be resized,
   and such use will eventually leak and/or corrupt memory.
*/
FSL_EXPORT int fsl_input_f_buffer( void * state, void * dest, fsl_size_t * n );


/**
   A generic streaming routine which copies data from an
   fsl_input_f() to an fsl_outpuf_f().

   Reads all data from inF() in chunks of an unspecified size and
   passes them on to outF(). It reads until inF() returns fewer
   bytes than requested. Returns the result of the last call to
   outF() or (only if reading fails) inF(). Returns FSL_RC_MISUSE
   if inF or ouF are NULL.

   Here is an example which basically does the same thing as the
   cat(1) command on Unix systems:

   @code
   fsl_stream( fsl_input_f_FILE, stdin, fsl_output_f_FILE, stdout );
   @endcode

   Or copy a FILE to a buffer:

   @code
   fsl_buffer myBuf = fsl_buffer_empty;
   rc = fsl_stream( fsl_input_f_FILE, stdin, fsl_output_f_buffer, &myBuf );
   // Note that on error myBuf might be partially populated.
   // Eventually clean up the buffer:
   fsl_buffer_clear(&myBuf);
   @endcode

*/
FSL_EXPORT int fsl_stream( fsl_input_f inF, void * inState,
                fsl_output_f outF, void * outState );

/**
   Consumes two input streams looking for differences.  It stops
   reading as soon as either or both streams run out of input or a
   byte-level difference is found.  It consumes input in chunks of
   an unspecified size, and after this returns the input cursor of
   the streams is not well-defined.  i.e. the cursor probably does
   not point to the exact position of the difference because this
   level of abstraction does not allow that unless we read byte by
   byte.

   Returns 0 if both streams emit the same amount of output and
   that ouput is bitwise identical, otherwise it returns non-0.
*/
FSL_EXPORT int fsl_stream_compare( fsl_input_f in1, void * in1State,
                        fsl_input_f in2, void * in2State );


/**
   A general-purpose buffer class, analog to Fossil v1's Blob
   class, but it is not called fsl_blob to avoid confusion with
   DB-side blobs. Buffers are used extensively in fossil to do
   everything from reading files to compressing artifacts to
   creating dynamically-formatted strings. Because they are such a
   pervasive low-level type, and have such a simple structure,
   their members (unlike most other structs in this API) may be
   considered public and used directly by client code (as long as
   they do not mangle their state, e.g. by setting this->capacity
   smaller than this->used!).

   General conventions of this class:

   - ALWAYS initialize them by copying fsl_buffer_empty or
   (depending on the context) fsl_buffer_empty_m. Failing to
   initialize them properly leads to undefined behaviour.

   - ALWAYS fsl_buffer_clear() buffers when done with
   them. Remember that failed routines which output to buffers
   might partially populate the buffer, so be sure to clean up on
   error cases.

   - The 'capacity' member specifies how much memory the buffer
   current holds in its 'mem' member.

   - The 'used' member specifies how much of the memory is actually
   "in use" by the client.

   - As a rule, the API tries to keep (used<capacity) and always
   (unless documented otherwise) tries to keep the memory buffer
   NUL-terminated (if it has any memory at all).

   - Use fsl_buffer_reuse() to keep memory around and reset the
   'used' amount to 0. Most rountines which write to buffers will
   re-use that memory if they can.

   This example demonstrates the difference between 'used' and
   'capacity' (error checking reduced to assert()ions for clarity):

   @code
   fsl_buffer b = fsl_buffer_empty;
   int rc = fsl_buffer_reserve(&b, 20);
   assert(0==rc);
   assert(b.capacity>=20); // it may reserve more!
   assert(0==b.used);
   rc = fsl_buffer_append(&b, "abc", 3);
   assert(0==rc);
   assert(3==b.used);
   assert(0==b.mem[b.used]); // API always NUL-terminates
   @endcode

   Potential TODO: add an allocator member which gets internally used
   for allocation of the buffer member. fossil(1) uses this approach,
   and swaps the allocator out as needed, to support a buffer pointing
   to memory it does not own, e.g. a slice of another buffer or to
   static memory, and then (re)allocate as necessary, e.g. to switch
   from static memory to dynamic. That may be useful in order to
   effectively port over some of the memory-intensive algos such as
   merging. That would not affect [much of] the public API, just how
   the buffer internally manages the memory. Certain API members would
   need to specify that the memory is not owned by the blob and needs
   to outlive the blob, though.

   @see fsl_buffer_reserve()
   @see fsl_buffer_append()
   @see fsl_buffer_appendf()
   @see fsl_buffer_cstr()
   @see fsl_buffer_size()
   @see fsl_buffer_capacity()
   @see fsl_buffer_clear()
   @see fsl_buffer_reuse()
*/
struct fsl_buffer {
  /**
     The raw memory owned by this buffer. It is this->capacity bytes
     long, of which this->used are considered "used" by the client.
     The difference beween (this->capacity - this->used) represents
     space the buffer has available for use before it will require
     another expansion/reallocation.
  */
  unsigned char * mem;
  /**
     Number of bytes allocated for this buffer.
  */
  fsl_size_t capacity;
  /**
     Number of "used" bytes in the buffer. This is generally
     interpreted as the string length of this->mem, and the buffer
     APIs which add data to a buffer always ensure that
     this->capacity is large enough to account for a trailing NUL
     byte in this->mem.

     Library routines which manipulate buffers must ensure that
     (this->used<=this->capacity) is always true, expanding the
     buffer if necessary. Much of the API assumes that precondition
     is always met, and any violation of it opens the code to
     undefined behaviour (which is okay, just don't ever break that
     precondition). Most APIs ensure that (used<capacity) is always
     true (as opposed to used<=capacity) because they add a
     trailing NUL byte which is not counted in the "used" length.
  */
  fsl_size_t used;

  /**
     Used by some routines to keep a cursor into this->mem.

     TODO: factor this back out and let those cases keep their own
     state. This is only used by fsl_input_f_buffer() (and that
     function cannot be implemented unless we add the cursor here
     or add another layer of state type specifically for it).

     TODO: No, don't do ^^^^. It turns out that the merge algo
     wants this as well.
  */
  fsl_size_t cursor;
};

/** Empty-initialized fsl_buffer instance, intended for const-copy
    initialization. */
#define fsl_buffer_empty_m {NULL,0U,0U,0U}

/** Empty-initialized fsl_buffer instance, intended for copy
    initialization. */
FSL_EXPORT const fsl_buffer fsl_buffer_empty;

/**
   A container for storing generic error state. It is used to
   propagate error state between layers of the API back to the
   client. i.e. they act as basic exception containers.

   @see fsl_error_set()
   @see fsl_error_get()
   @see fsl_error_move()
   @see fsl_error_clear()
*/
struct fsl_error {
  /**
     Error message text is stored in this->msg.mem. The usable text
     part is this->msg.used bytes long.
  */
  fsl_buffer msg;
  /**
     Error code, generally assumed to be a fsl_rc_e value.
  */
  int code;
};

/** Empty-initialized fsl_error instance, intended for const-copy initialization. */
#define fsl_error_empty_m {fsl_buffer_empty_m,0}

/** Empty-initialized fsl_error instance, intended for copy initialization. */
FSL_EXPORT const fsl_error fsl_error_empty;

/**
   Populates err with the given code and formatted string, replacing
   any existing state. If fmt==NULL then fsl_rc_cstr(rc) is used to
   get the error string.

   Returns code on success, some other non-0 code on error.

   As a special case, if 0==code then fmt is ignored and the error
   state is cleared. This will not free any memory held by err but
   will re-set its string to start with a NUL byte, ready for
   re-use later on.

   As a special case, if code==FSL_RC_OOM then fmt is ignored
   to avoid a memory allocation (which would presumably fail).

   @see fsl_error_get()
   @see fsl_error_clear()
   @see fsl_error_move()
*/
FSL_EXPORT int fsl_error_set( fsl_error * err, int code, char const * fmt,
                   ... );

/**
   va_list counterpart to fsl_error_set().
*/
FSL_EXPORT int fsl_error_setv( fsl_error * err, int code, char const * fmt,
                    va_list args );

/**
   Fetches the error state from err. If !err it returns
   FSL_RC_MISUSE without side-effects, else it returns err's current
   error code.

   If str is not NULL then *str will be assigned to the raw
   (NUL-terminated) error string (which might be empty or even
   NULL). The memory for the string is owned by err and may be
   invalidated by any calls which take err as a non-const parameter
   OR which might modify it indirectly through a container object,
   so the client is required to copy it if it is needed for later
   on.

   If len is not NULL then *len will be assigned to the length of
   the returned string (in bytes).

   @see fsl_error_set()
   @see fsl_error_clear()
   @see fsl_error_move()
*/
FSL_EXPORT int fsl_error_get( fsl_error const * err, char const ** str, fsl_size_t * len );

/**
   Frees up any resources owned by err and sets its error code to 0,
   but does not free err. This is harmless no-op if !err or if err
   holds no dynamically allocated no memory.

   @see fsl_error_set()
   @see fsl_error_get()
   @see fsl_error_move()
   @see fsl_error_reset()
*/
FSL_EXPORT void fsl_error_clear( fsl_error * err );

/**
   Sets err->code to 0 and resets its buffer, but keeps any
   err->msg memory around for later re-use.

   @see fsl_error_clear()
*/
FSL_EXPORT void fsl_error_reset( fsl_error * err );

/**
   Copies the error state from src to dest. If dest contains state, it is
   cleared/recycled by this operation.

   Returns 0 on success, FSL_RC_MISUSE if either argument is NULL
   or if (src==dest), and FSL_RC_OOM if allocation of the message
   string fails.

   As a special case, if src->code==FSL_RC_OOM, then the code is
   copied but the message bytes (if any) are not (under the
   assumption that we have no more memory).
*/
FSL_EXPORT int fsl_error_copy( fsl_error const * src, fsl_error * dest );

/**
   Moves the error state from one fsl_error object to
   another, intended as an allocation optimization when
   propagating error state up the API.

   This "uplifts" an error from the 'from' object to the 'to'
   object. After this returns 'to' will contain the prior error state
   of 'from' and 'from' will contain the old error message memory of
   'to'. 'from' will be re-set to the non-error state (its buffer
   memory is kept intact for later reuse, though).

   Results are undefined if either parameter is NULL or either is
   not properly initialized. i.e. neither may refer to uninitialized
   memory. Copying fsl_error_empty at declaration-time is a simple
   way to ensure that instances are cleanly initialized.
*/
FSL_EXPORT void fsl_error_move( fsl_error * from, fsl_error * to );

/**
   Returns the given Unix Epoch timestamp value as its approximate
   Julian Day value. Note that the calculation does not account for
   leap seconds.
*/
FSL_EXPORT double fsl_unix_to_julian( fsl_time_t unixEpoch );

/**
   Returns the current Unix Epoch time converted to its approximate
   Julian form. Equivalent to fsl_unix_to_julian(time(0)). See
   fsl_unix_to_julian() for details. Note that the returned time
   has seconds, not milliseconds, precision.
*/
FSL_EXPORT double fsl_julian_now();

#if 0
/** UNTESTED, possibly broken vis-a-vis timezone conversion.

    Returns the given Unix Epoch time value formatted as an ISO8601
    string.  Returns NULL on allocation error, else a string 19
    bytes long plus a terminating NUL
    (e.g. "2013-08-19T20:35:49"). The returned memory must
    eventually be freed using fsl_free().
*/
FSL_EXPORT char * fsl_unix_to_iso8601( fsl_time_t j );
#endif

/**
   Returns non-0 (true) if the first 10 digits of z _appear_ to
   form the start of an ISO date string (YYYY-MM-DD). Whether or
   not the string is really a valid date is left for downstream
   code to determine. Returns 0 (false) in all other cases,
   including if z is NULL.
*/
FSL_EXPORT char fsl_str_is_date(const char *z);


/**
   Checks if z is syntactically a time-format string in the format:

   [Y]YYYY-MM-DD

   (Yes, the year may be five-digits, left-padded with a zero for
   years less than 9999.)

   Returns a positive value if the YYYYY part has five digits, a
   negative value if it has four. It returns 0 (false) if z does not
   match that pattern.

   If it returns a negative value, the MM part of z starts at byte offset
   (z+5), and a positive value means the MM part starts at (z+6).

   z need not be NUL terminated - this function does not read past
   the first invalid byte. Thus is can be used on, e.g., full
   ISO8601-format strings. If z is NULL, 0 is returned.
*/
FSL_EXPORT int fsl_str_is_date2(const char *z);


/**
   Reserves at least n bytes of capacity in buf. Returns 0 on
   success, FSL_RC_OOM if allocation fails, FSL_RC_MISUSE if !buf.

   This does not change buf->used, nor will it shrink the buffer
   (reduce buf->capacity) unless n is 0, in which case it
   immediately frees buf->mem and sets buf->capacity and buf->used
   to 0.

   @see fsl_buffer_resize()
   @see fsl_buffer_clear()
*/
FSL_EXPORT int fsl_buffer_reserve( fsl_buffer * buf, fsl_size_t n );

/**
   Convenience equivalent of fsl_buffer_reserve(buf,0).
   This a no-op if buf==NULL.
*/
FSL_EXPORT void fsl_buffer_clear( fsl_buffer * buf );

/**
   Resets buf->used to 0 and sets buf->mem[0] (if buf->mem is not
   NULL) to 0. Does not (de)allocate memory, only changes the
   logical "used" size of the buffer. Returns its argument.

   Achtung for fossil(1) porters: this function's semantics are much
   different from the fossil's blob_reset(). To get those semantics,
   use fsl_buffer_reserve(buf, 0) or its convenience form
   fsl_buffer_clear(). (This function _used_ to be called
   fsl_buffer_reset(), but it was renamed in the hope of avoiding
   related confusion.)
*/
FSL_EXPORT fsl_buffer * fsl_buffer_reuse( fsl_buffer * buf );

/**
   Similar to fsl_buffer_reserve() except that...

   - It does not free all memory when n==0. Instead it essentially
   makes the memory a length-0, NUL-terminated string.

   - It will try to shrink (realloc) buf's memory if (n<buf->capacity).

   - It sets buf->capacity to (n+1) and buf->used to n. This routine
   allocates one extra byte to ensure that buf is always
   NUL-terminated.

   - On success it always NUL-terminates the buffer at
   offset buf->used.

   Returns 0 on success, FSL_RC_MISUSE if !buf, FSL_RC_OOM if
   (re)allocation fails.

   @see fsl_buffer_reserve()
   @see fsl_buffer_clear()
*/
FSL_EXPORT int fsl_buffer_resize( fsl_buffer * buf, fsl_size_t n );

/**
   Swaps the contents of the left and right arguments. Results are
   undefined if either argument is NULL or points to uninitialized
   memory.
*/
FSL_EXPORT void fsl_buffer_swap( fsl_buffer * left, fsl_buffer * right );

/**
   Similar fsl_buffer_swap() but it also optionally frees one of
   the buffer's memories after swapping them. If clearWhich is
   negative then the left buffer (1st arg) is cleared _after_
   swapping (i.e., the NEW left hand side gets cleared). If
   clearWhich is greater than 0 then the right buffer (2nd arg) is
   cleared _after_ swapping (i.e. the NEW right hand side gets
   cleared). If clearWhich is 0, this function behaves identically
   to fsl_buffer_swap().

   A couple examples should clear this up:

   @code
   fsl_buffer_swap_free( &b1, &b2, -1 );
   @endcode

   Swaps the contents of b1 and b2, then frees the contents
   of the left-side buffer (b1).

   @code
   fsl_buffer_swap_free( &b1, &b2, 1 );
   @endcode

   Swaps the contents of b1 and b2, then frees the contents
   of the right-side buffer (b2).
*/
FSL_EXPORT void fsl_buffer_swap_free( fsl_buffer * left, fsl_buffer * right,
                                      int clearWhich );  

/**
   Appends the first n bytes of src, plus a NUL byte, to b,
   expanding b as necessary and incrementing b->used by n. If n is
   less than 0 then the equivalent of fsl_strlen((char const*)src)
   is used to calculate the length.

   If n is 0 (or negative and !*src), this function ensures that
   b->mem is not NULL and is NUL-terminated, so it may allocate
   to have space for that NUL byte.

   src may only be NULL if n==0. If passed (src==NULL, n!=0) then
   FSL_RC_RANGE is returned.

   Returns 0 on success, FSL_RC_MISUSE if !f, !b, or !src,
   FSL_RC_OOM if allocation of memory fails.

   If this function succeeds, it guarantees that it NUL-terminates
   the buffer (but that the NUL terminator is not counted in
   b->used). 

   @see fsl_buffer_appendf()
   @see fsl_buffer_reserve()
*/
FSL_EXPORT int fsl_buffer_append( fsl_buffer * b,
                                  void const * src, fsl_int_t n );

/**
   Uses fsl_appendf() to append formatted output to the given
   buffer.  Returns 0 on success, FSL_RC_MISUSE if !f or !dest, and
   FSL_RC_OOM if an allocation fails while expanding dest.

   @see fsl_buffer_append()
   @see fsl_buffer_reserve()
*/
FSL_EXPORT int fsl_buffer_appendf( fsl_buffer * dest,
                                   char const * fmt, ... );

/** va_list counterpart to fsl_buffer_appendfv(). */
FSL_EXPORT int fsl_buffer_appendfv( fsl_buffer * dest,
                                    char const * fmt, va_list args );

/**
   Compresses the first pIn->used bytes of pIn to pOut. It is ok for
   pIn and pOut to be the same blob.

   pOut must either be the same as pIn or else a properly
   initialized buffer. Any prior contents will be freed or their
   memory reused.

   Results are undefined if any argument is NULL.

   Returns 0 on success, FSL_RC_OOM on allocation error, and FSL_RC_ERROR
   if the lower-level compression routines fail.

   Use fsl_buffer_uncompress() to uncompress the data. The data is
   encoded with a big-endian, unsigned 32-bit length as the first four
   bytes (holding its uncomressed size), and then the data as
   compressed by zlib.

   TODO: if pOut!=pIn1 then re-use pOut's memory, if it has any.

   @see fsl_buffer_compress2()
   @see fsl_buffer_uncompress()
   @see fsl_buffer_is_compressed()
*/
FSL_EXPORT int fsl_buffer_compress(fsl_buffer const *pIn, fsl_buffer *pOut);

/**
   Compress the concatenation of a blobs pIn1 and pIn2 into pOut.

   pOut must be either empty (cleanly initialized or newly
   recycled) or must be the same as either pIn1 or pIn2.

   Results are undefined if any argument is NULL.

   Returns 0 on success, FSL_RC_OOM on allocation error, and FSL_RC_ERROR
   if the lower-level compression routines fail.

   TODO: if pOut!=(pIn1 or pIn2) then re-use its memory, if it has any.

   @see fsl_buffer_compress()
   @see fsl_buffer_uncompress()
   @see fsl_buffer_is_compressed()
*/
FSL_EXPORT int fsl_buffer_compress2(fsl_buffer const *pIn1,
                                    fsl_buffer const *pIn2,
                                    fsl_buffer *pOut);

/**
   Uncompress buffer pIn and store the result in pOut. It is ok for
   pIn and pOut to be the same buffer. Returns 0 on success. On
   error pOut is not modified.

   pOut must be either cleanly initialized/empty or the same as pIn.

   Results are undefined if any argument is NULL.

   Returns 0 on success, FSL_RC_OOM on allocation error, and
   FSL_RC_ERROR if the lower-level decompression routines fail.

   TODO: if pOut!=(pIn1 or pIn2) then re-use its memory, if it has any.

   @see fsl_buffer_compress()
   @see fsl_buffer_compress2()
   @see fsl_buffer_is_compressed()
*/
FSL_EXPORT int fsl_buffer_uncompress(fsl_buffer const *pIn, fsl_buffer *pOut);

/**
   Returns true if this function believes that mem (which must be
   at least len bytes of valid memory long) appears to have been
   compressed by fsl_buffer_compress() or equivalent. This is not a
   100% reliable check - it could potentially have false positives
   on certain inputs, but that is thought to be unlikely (at least
   for text data).

   Returns 0 if mem is NULL.
*/
FSL_EXPORT bool fsl_data_is_compressed(unsigned char const * mem, fsl_size_t len);

/**
   Equivalent to fsl_data_is_compressed(buf->mem, buf->used).
*/
FSL_EXPORT bool fsl_buffer_is_compressed(fsl_buffer const * buf);

/**
   If fsl_data_is_compressed(mem,len) returns true then this function
   returns the uncompressed size of the data, else it returns a negative
   value.
*/
FSL_EXPORT fsl_int_t fsl_data_uncompressed_size(unsigned char const *mem, fsl_size_t len);

/**
   The fsl_buffer counterpart of fsl_data_uncompressed_size().
*/
FSL_EXPORT fsl_int_t fsl_buffer_uncompressed_size(fsl_buffer const * b);

/**
   Equivalent to ((char const *)b->mem), but returns NULL if
   !b. The returned string is effectively b->used bytes long unless
   the user decides to apply his own conventions. Note that the buffer APIs
   generally assure that buffers are NUL-terminated, meaning that strings
   returned from this function can (for the vast majority of cases)
   assume that the returned string is NUL-terminated (with a string length
   of b->used _bytes_).

   @see fsl_buffer_str()
   @see fsl_buffer_cstr2()
*/
FSL_EXPORT char const * fsl_buffer_cstr(fsl_buffer const *b);

/**
   If buf is not NULL and has any memory allocated to it, that
   memory is returned. If both b and len are not NULL then *len is
   set to b->used. If b has no dynamic memory then NULL is returned
   and *len (if len is not NULL) is set to 0.

   @see fsl_buffer_str()
   @see fsl_buffer_cstr()
*/
FSL_EXPORT char const * fsl_buffer_cstr2(fsl_buffer const *b, fsl_size_t * len);

/**
   Equivalent to ((char *)b->mem). The returned memory is effectively
   b->used bytes long unless the user decides to apply their own
   conventions.
*/
FSL_EXPORT char * fsl_buffer_str(fsl_buffer const *b);
/**
   "Takes" the memory refered to by the given buffer, transfering
   ownership to the caller. After calling this, b's state will be
   empty.
*/
FSL_EXPORT char * fsl_buffer_take(fsl_buffer *b);

/**
   Returns the "used" size of b, or 0 if !b.
*/
FSL_EXPORT fsl_size_t fsl_buffer_size(fsl_buffer const * b);

/**
   Returns the current capacity of b, or 0 if !b.
*/
FSL_EXPORT fsl_size_t fsl_buffer_capacity(fsl_buffer const * b);

/**
   Compares the contents of buffers lhs and rhs using memcmp(3)
   semantics. Return negative, zero, or positive if the first
   buffer is less then, equal to, or greater than the second.
   Results are undefined if either argument is NULL.

   When buffers of different length match on the first N bytes,
   where N is the shorter of the two buffers' lengths, it treats the
   shorter buffer as being "less than" the longer one.
*/
FSL_EXPORT int fsl_buffer_compare(fsl_buffer const * lhs, fsl_buffer const * rhs);

/**
   Bitwise-compares the contents of b against the file named by
   zFile.  Returns 0 if they have the same size and contents, else
   non-zero.  This function has no way to report if zFile cannot be
   opened, and any error results in a non-0 return value. No
   interpretation/canonicalization of zFile is performed - it is
   used as-is.

   This resolves symlinks and returns non-0 if zFile refers (after
   symlink resolution) to a non-file.

   If zFile does not exist, is not readable, or has a different
   size than b->used, non-0 is returned without opening/reading the
   file contents. If a content comparison is performed, it is
   streamed in chunks of an unspecified (but relatively small)
   size, so it does not need to read the whole file into memory
   (unless it is smaller than the chunk size).
*/
FSL_EXPORT int fsl_buffer_compare_file( fsl_buffer const * b, char const * zFile );

/**
   Compare two buffers in constant (a.k.a. O(1)) time and return
   zero if they are equal.  Constant time comparison only applies
   for buffers of the same length.  If lengths are different,
   immediately returns 1. This operation is provided for cases
   where the timing/duration of fsl_buffer_compare() (or an
   equivalent memcmp()) might inadvertently leak security-relevant
   information.  Specifically, it address the concern that
   attackers can use timing differences to check for password
   misses, to narrow down an attack to passwords of a specific
   length or content properties.
*/
FSL_EXPORT int fsl_buffer_compare_O1(fsl_buffer const * lhs, fsl_buffer const * rhs);

/**
   Overwrites dest's contents with a copy of those from src
   (reusing dest's memory if it has any). Results are undefined if
   either pointer is NULL or invalid. Returns 0 on success,
   FSL_RC_OOM on allocation error.
*/
FSL_EXPORT int fsl_buffer_copy( fsl_buffer const * src, fsl_buffer * dest );


/**
   Apply the delta in pDelta to the original content pOriginal to
   generate the target content pTarget. All three pointers must point
   to properly initialized memory.

   If pTarget==pOriginal then this is a destructive operation,
   replacing the original's content with its new form.

   Return 0 on success.

   @see fsl_buffer_delta_apply()
   @see fsl_delta_apply()
   @see fsl_delta_apply2()
*/
FSL_EXPORT int fsl_buffer_delta_apply( fsl_buffer const * pOriginal,
                                       fsl_buffer const * pDelta,
                                       fsl_buffer * pTarget);

/**
   Identical to fsl_buffer_delta_apply() except that if delta
   application fails then any error messages/codes are written to
   pErr if it is not NULL. It is rare that delta application fails
   (only if the inputs are invalid, e.g. do not belong together or
   are corrupt), but when it does, having error information can be
   useful.

   @see fsl_buffer_delta_apply()
   @see fsl_delta_apply()
   @see fsl_delta_apply2()
*/
FSL_EXPORT int fsl_buffer_delta_apply2( fsl_buffer const * pOriginal,
                                        fsl_buffer const * pDelta,
                                        fsl_buffer * pTarget,
                                        fsl_error * pErr);


/**
   Uses a fsl_input_f() function to buffer input into a fsl_buffer.

   dest must be a non-NULL, initialized (though possibly empty)
   fsl_buffer object. Its contents, if any, will be overwritten by
   this function, and any memory it holds might be re-used.

   The src function is called, and passed the state parameter, to
   fetch the input. If it returns non-0, this function returns that
   error code. src() is called, possibly repeatedly, until it
   reports that there is no more data.

   Whether or not this function succeeds, dest still owns any memory
   pointed to by dest->mem, and the client must eventually free it
   by calling fsl_buffer_reserve(dest,0).

   dest->mem might (and possibly will) be (re)allocated by this
   function, so any pointers to it held from before this call might
   be invalidated by this call.

   On error non-0 is returned and dest may bge partially populated.

   Errors include:

   dest or src are NULL (FSL_RC_MISUSE)

   Allocation error (FSL_RC_OOM)

   src() returns an error code

   Whether or not the state parameter may be NULL depends on the src
   implementation requirements.

   On success dest will contain the contents read from the input
   source. dest->used will be the length of the read-in data, and
   dest->mem will point to the memory. dest->mem is automatically
   NUL-terminated if this function succeeds, but dest->used does not
   count that terminator. On error the state of dest->mem must be
   considered incomplete, and is not guaranteed to be
   NUL-terminated.

   Example usage:

   @code
   fsl_buffer buf = fsl_buffer_empty;
   int rc = fsl_buffer_fill_from( &buf,
   fsl_input_f_FILE,
   stdin );
   if( rc ){
   fprintf(stderr,"Error %d (%s) while filling buffer.\n",
   rc, fsl_rc_cstr(rc));
   fsl_buffer_reserve( &buf, 0 );
   return ...;
   }
   ... use the buf->mem ...
   ... clean up the buffer ...
   fsl_buffer_reserve( &buf, 0 );
   @endcode

   To take over ownership of the buffer's memory, do:

   @code
   void * mem = buf.mem;
   buf = fsl_buffer_empty;
   @endcode

   In which case the memory must eventually be passed to fsl_free()
   to free it.
*/
FSL_EXPORT int fsl_buffer_fill_from( fsl_buffer * dest, fsl_input_f src, void * state );

/**
   A fsl_buffer_fill_from() proxy which overwrite's dest->mem with
   the contents of the given FILE handler (which must be opened for
   read access).  Returns 0 on success, after which dest->mem
   contains dest->used bytes of content from the input source. On
   error dest may be partially filled.
*/
FSL_EXPORT int fsl_buffer_fill_from_FILE( fsl_buffer * dest, FILE * src );

/**
   A wrapper for fsl_buffer_fill_from_FILE() which gets its input
   from the given file name. It uses fsl_fopen() to open the file,
   so it supports "-" as an alias for stdin.

   Uses fsl_fopen() to open the file, so it supports the name '-'
   as an alias for stdin.
*/
FSL_EXPORT int fsl_buffer_fill_from_filename( fsl_buffer * dest, char const * filename );    

/**
   Writes the given buffer to the given filename. Returns 0 on success,
   FSL_RC_MISUSE if !b or !fname, FSL_RC_IO if opening or writing fails.

   Uses fsl_fopen() to open the file, so it supports the name '-'
   as an alias for stdout.
*/
FSL_EXPORT int fsl_buffer_to_filename( fsl_buffer const * b, char const * fname );

/**
   Copy N lines of text from pFrom into pTo. The copy begins at the
   current pFrom->cursor position. pFrom->cursor is left pointing at
   the first character past the last \\n copied. (Modification of the
   cursor is why pFrom is not const.)

   If pTo==NULL then this routine simply skips over N lines.

   Returns 0 if it copies lines or does nothing (because N is 0 or
   pFrom's contents have been exhausted). Copying fewer lines than
   requested (because of EOF) is not an error. Returns non-0 only on
   allocation error. Results are undefined if pFrom is NULL or not
   properly initialized.

   @see fsl_buffer_stream_lines()
*/
FSL_EXPORT int fsl_buffer_copy_lines(fsl_buffer *pTo, fsl_buffer *pFrom, fsl_size_t N);

/**
   Works identically to fsl_buffer_copy_lines() except that it sends
   its output to the fTo output function. If fTo is NULL then it
   simply skips over N lines.

   @see fsl_buffer_copy_lines()
*/
FSL_EXPORT int fsl_buffer_stream_lines(fsl_output_f fTo, void * toState,
                                       fsl_buffer *pFrom, fsl_size_t N);


/**
   Works like fsl_appendfv(), but appends all output to a
   dynamically-allocated string, expanding the string as necessary
   to collect all formatted data. The returned NUL-terminated string
   is owned by the caller and it must be cleaned up using
   fsl_free(...). If !fmt, NULL is returned. It is conceivable that
   it returns NULL on a zero-length formatted string, e.g.  (%.*s)
   with (0,"...") as arguments, but it will only do that if the
   whole format string resolves to empty.
*/
FSL_EXPORT char * fsl_mprintf( char const * fmt, ... );

/**
   va_list counterpart to fsl_mprintf().
*/
FSL_EXPORT char * fsl_mprintfv(char const * fmt, va_list vargs );

/**
   An sprintf(3) clone which uses fsl_appendf() for the formatting.
   Outputs at most n bytes to dest and returns the number of bytes
   output. Returns a negative value if !dest or !fmt. Returns 0
   without side-effects if !n or !*fmt.

   If the destination buffer is long enough (this function returns
   a non-negative value less than n), this function NUL-terminates it.
   If it returns n then there was no space for the terminator.
*/
FSL_EXPORT fsl_int_t fsl_snprintf( char * dest, fsl_size_t n, char const * fmt, ... );

/**
   va_list counterpart to fsl_snprintf()
*/
FSL_EXPORT fsl_int_t fsl_snprintfv( char * dest, fsl_size_t n, char const * fmt, va_list args );

/**
   Equivalent to fsl_strndup(src,-1).
*/
FSL_EXPORT char * fsl_strdup( char const * src );

/**
   Similar to strndup(3) but returns NULL if !src.  The returned
   memory must eventually be passed to fsl_free(). Returns NULL on
   allocation error. If len is less than 0 and src is not NULL then
   fsl_strlen() is used to calculate its length.

   If src is not NULL but len is 0 then it will return an empty
   (length-0) string, as opposed to NULL.
*/
FSL_EXPORT char * fsl_strndup( char const * src, fsl_int_t len );

/**
   Equivalent to strlen(3) but returns 0 if src is NULL.
   Note that it counts bytes, not UTF characters.
*/
FSL_EXPORT fsl_size_t fsl_strlen( char const * src );

/**
   Like strcmp(3) except that it accepts NULL pointers.  NULL sorts
   before all non-NULL string pointers.  Also, this routine
   performs a binary comparison that does not consider locale.
*/
FSL_EXPORT int fsl_strcmp( char const * lhs, char const * rhs );

/**
   Equivalent to fsl_strcmp(), but with a signature suitable
   for use as a generic comparison function (e.g. for use with
   qsort() and search algorithms).
*/
FSL_EXPORT int fsl_strcmp_cmp( void const * lhs, void const * rhs );

/**
   Case-insensitive form of fsl_strcmp().

   @implements fsl_generic_cmp_f()
*/
FSL_EXPORT int fsl_stricmp(const char *zA, const char *zB);

/**
   Equivalent to fsl_stricmp(), but with a signature suitable
   for use as a generic comparison function (e.g. for use with
   qsort() and search algorithms).

   @implements fsl_generic_cmp_f()
*/
FSL_EXPORT int fsl_stricmp_cmp( void const * lhs, void const * rhs );

/**
   fsl_strcmp() variant which compares at most nByte bytes of the
   given strings, case-insensitively.  If nByte is less than 0 then
   fsl_strlen(zB) is used to obtain the length for comparision
   purposes.
*/
FSL_EXPORT int fsl_strnicmp(const char *zA, const char *zB, fsl_int_t nByte);

/**
   fsl_strcmp() variant which compares at most nByte bytes of the
   given strings, case-sensitively. Returns 0 if nByte is 0.
*/
FSL_EXPORT int fsl_strncmp(const char *zA, const char *zB, fsl_size_t nByte);

/**
   Equivalent to fsl_strncmp(lhs, rhs, X), where X is either
   FSL_STRLEN_SHA1 or FSL_STRLEN_K256: if both lhs and rhs are
   longer than FSL_STRLEN_SHA1 then they are assumed to be
   FSL_STRLEN_K256 bytes long and are compared as such, else they
   are assumed to be FSL_STRLEN_SHA1 bytes long and compared as
   such.

   Potential FIXME/TODO: if their lengths differ, i.e. one is v1 and
   one is v2, compare them up to their common length then, if they
   still compare equivalent, treat the shorter one as less-than the
   longer.
*/
FSL_EXPORT int fsl_uuidcmp( fsl_uuid_cstr lhs, fsl_uuid_cstr rhs );

/**
   Returns false if s is NULL or starts with any of (0 (NUL), '0'
   (ASCII character zero), 'f', 'n', "off"), case-insensitively,
   else it returns true.
*/
FSL_EXPORT bool fsl_str_bool( char const * s );

/**
   Flags for use with fsl_db_open() and friends.
*/
enum fsl_open_flags {
/**
   The "no flags" value.
*/
FSL_OPEN_F_NONE = 0,
/**
   Flag for fsl_db_open() specifying that the db should be opened
   in read-only mode.
*/
FSL_OPEN_F_RO = 0x01,
/**
   Flag for fsl_db_open() specifying that the db should be opened
   in read-write mode, but should not create the db if it does
   not already exist.
*/
FSL_OPEN_F_RW = 0x02,
/**
   Flag for fsl_db_open() specifying that the db should be opened in
   read-write mode, creating the db if it does not already exist.
*/
FSL_OPEN_F_CREATE = 0x04,
/**
   Shorthand for RW+CREATE flags.
*/
FSL_OPEN_F_RWC = FSL_OPEN_F_RW | FSL_OPEN_F_CREATE,
/**
   Tells fsl_repo_open_xxx() to confirm that the db
   is a repository.
*/
FSL_OPEN_F_SCHEMA_VALIDATE = 0x20,

/**
   Used by fsl_db_open() to to tell 1the underlying db connection to
   trace all SQL to stdout. This is often useful for testing,
   debugging, and learning about what's going on behind the scenes.
*/
FSL_OPEN_F_TRACE_SQL = 0x40
};


/**
   _Almost_ equivalent to fopen(3) but:

   - expects name to be UTF8-encoded.

   - If name=="-", it returns one of stdin or stdout, depending on
   the mode string: stdout is returned if 'w' or '+' appear,
   otherwise stdin.

   If it returns NULL, the errno "should" contain a description of
   the problem unless the problem was argument validation. Pass it
   to fsl_errno_to_rc() to convert that into an API-conventional
   error code.

   If at all possible, use fsl_close() (as opposed to fclose()) to
   close these handles, as it has logic to skip closing the
   standard streams.

   Potential TODOs:

   - extend mode string to support 'x', meaning "exclusive", analog
   to open(2)'s O_EXCL flag. Barring race conditions, we have
   enough infrastructure to implement that. (It turns out that
   glibc's fopen() supports an 'x' with exactly this meaning.)

   - extend mode to support a 't', meaning "temporary". The idea
   would be that we delete the file from the FS right after
   opening, except that Windows can't do that.
*/
FSL_EXPORT FILE * fsl_fopen(char const * name, char const *mode);

/**
   Passes f to fclose(3) unless f is NULL or one of the C-standard
   (stdin, stdout, stderr) handles, in which cases it does nothing
   at all.
*/
FSL_EXPORT void fsl_fclose(FILE * f);

/**
   @typedef fsl_int_t (*fsl_appendf_f)( void * state, char const * data, fsl_int_t n )

   The fsl_appendf_f typedef is used to provide fsl_appendfv() with
   a flexible output routine, so that it can be easily send its
   output to arbitrary targets.

   The policies which implementations need to follow are:

   - state is an implementation-specific pointer (may be 0) which
   is passed to fsl_appendf(). fsl_appendfv() doesn't know what
   this argument is but passes it to its fsl_appendf_f argument.
   Typically this pointer will be an object or resource handle to
   which string data is pushed.

   - The 'data' parameter is the data to append. The API does not
   currently guaranty that data containing embeded NULs will survive
   the ride through fsl_appendf() and its delegates.

   - n is the number of bytes to read from data. The fact that n is
   of a signed type is historical. It can be treated as an unsigned
   type for purposes of fsl_appendf().

   - Returns, on success, the number of bytes appended (may be 0).

   - Returns, on error, an implementation-specified negative
   number.  Returning a negative error code will cause
   fsl_appendfv() to stop processing and return. Note that 0 is a
   success value (some printf format specifiers do not add anything
   to the output).
*/
typedef fsl_int_t (*fsl_appendf_f)( void * state, char const * data,
                                    fsl_int_t n );

/**
   This function works similarly to classical printf
   implementations, but instead of outputing somewhere specific, it
   uses a callback function to push its output somewhere. This
   allows it to be used for arbitrary external representations. It
   can be used, for example, to output to an external string, a UI
   widget, or file handle (it can also emulate printf by outputing
   to stdout this way).

   INPUTS:

   pfAppend: The is a fsl_appendf_f function which is responsible
   for accumulating the output. If pfAppend returns a negative
   value then processing stops immediately.

   pfAppendArg: is ignored by this function but passed as the first
   argument to pfAppend. pfAppend will presumably use it as a data
   store for accumulating its string.

   fmt: This is the format string, as in the usual printf(3), except
   that it supports more options (detailed below).

   ap: This is a pointer to a list of arguments.  Same as in
   vprintf() and friends.


   OUTPUTS:

   The return value is the total number of characters sent to the
   function "func", or a negative number on a pre-output error. If
   this function returns an integer greater than 1 it is in general
   impossible to know if all of the elements were output. As such
   failure can only happen if the callback function returns an
   error or if one of the formatting options needs to allocate
   memory and cannot. Both of those cases are very rare in a
   printf-like context, so this is not considered to be a
   significant problem. (The same is true for any classical printf
   implementations.) Clients may use their own state objects which
   can propagate errors from their own internals back to the
   caller, but generically speaking it is difficult to trace errors
   back through this routine. Then again, in practice that has
   never proven to be a problem.

   Most printf-style specifiers work as they do in standard printf()
   implementations. There might be some very minor differences, but
   the more common format specifiers work as most developers expect
   them to. In addition...

   Current (documented) printf extensions:

   (If you are NOT reading this via doxygen-processed sources: the
   percent signs below are doubled for the sake of doxygen, and
   each pair refers to only a single percent sign in the format
   string.)

   %%z works like %%s, but takes a non-const (char *) and deletes
   the string (using fsl_free()) after appending it to the output.

   %%h (HTML) works like %%s but converts certain characters (namely
   '<' and '&') to their HTML escaped equivalents.

   %%t (URL encode) works like %%s but converts certain characters
   into a representation suitable for use in an HTTP URL. (e.g. ' ' 
   gets converted to %%20)

   %%T (URL decode) does the opposite of %%t - it decodes
   URL-encoded strings and outputs their decoded form. ACHTUNG:
   fossil(1) interprets this the same as %%t except that it leaves
   '/' characters unescaped (did that change at some point? This
   code originally derived from that one some years ago!). It is
   still to be determined whether we "really need" that behaviour
   (we don't really need either one, seeing as the library is not
   CGI-centric like fossil(1) is).

   %%r requires an int and renders it in "ordinal form". That is,
   the number 1 converts to "1st" and 398 converts to "398th".

   %%q quotes a string as required for SQL. That is, '\''
   characters get doubled. It does NOT included the outer quotes
   and NULL values get replaced by the string "(NULL) (without
   quotes). See %%Q...

   %%Q works like %%q, but includes the outer '\'' characters and
   NULL pointers get output as the string literal "NULL" (without
   quotes), i.e. an SQL NULL.

   %%S works like %%.16s. It is intended for fossil hashes. The '!'
   modifier removes the length limit, resulting in the whole hash
   (making this formatting option equivalent to %%s).  (Sidebar: in
   fossil(1) this length is runtime configurable but that requires
   storing that option in global state, which is not an option for
   this implementation.)

   %%/: works like %%s but normalizes path-like strings by
   replacing backslashes with the One True Slash.

   %%b: works like %%s but takes its input from a (fsl_buffer
   const*) argument.

   %%B: works like %%Q but takes its input from a (fsl_buffer
   const*) argument.

   %%F: works like %%s but runs the output through
   fsl_bytes_fossilize().  This requires dynamic memory allocation, so
   is less efficient than re-using a client-provided buffer with
   fsl_bytes_fossilize() if the client needs to fossilize more than
   one element.

   %%j: works like %%s but JSON-encodes the string. It does not
   include the outer quotation marks by default, but using the '!'
   flag, i.e. %%!j, causes those to be added. The length and precision
   flags are NOT supported for this format. Results are undefined if
   given input which is not legal UTF8. By default non-ASCII
   characters with values less than 0xffff are emitted as as literal
   characters (no escaping), but the '#' modifier flag will cause it
   to emit such characters in the \\u#### form. It always encodes
   characters above 0xFFFF as UTF16 surrogate pairs (as JSON
   requires). Invalid UTF8 characters may get converted to '?' or may
   produce invalid JSON output. As a special case, if the value is NULL
   pointer, it resolves to "null" without quotes (regardless of the '!'
   modifier).

   Some of these extensions may be disabled by setting certain macros
   when compiling appendf.c (see that file for details).

   FIXME? fsl_appendf_f() is an artifact of older code from which
   this implementation derives. The first parameter should arguably
   be replaced with fsl_output_f(), which does the same thing _but_
   has different return semantics (more reliable, because the
   current semantics report partial success as success in some
   cases). Doing this would require us to change the return
   semantics of this function, but that wouldn't necessarily be a
   bad thing (we don't rely on sprintf()-like return semantics all
   that much, if at all). Or we just add a proxy which forwards to
   a fsl_output_f(). (Oh, hey, that's what fsl_outputf() does.) But
   that doesn't catch certain types of errors (namely allocation
   failures) which can happen as side-effects of some formatting
   operations.

   Potential TODO: add fsl_bytes_fossilize_out() which works like
   fsl_bytes_fossilize() but sends its output to an fsl_output_f()
   and fsl_appendf_f(), so that this routine doesn't need to alloc
   for that case.
*/
FSL_EXPORT fsl_int_t fsl_appendfv(fsl_appendf_f pfAppend, void * pfAppendArg,
                                  const char *fmt, va_list ap );

/**
   Identical to fsl_appendfv() but takes an ellipses list (...)
   instead of a va_list.
*/
FSL_EXPORT fsl_int_t fsl_appendf(fsl_appendf_f pfAppend,
                      void * pfAppendArg,
                      const char *fmt,
                      ... )
#if 0
/* Would be nice, but complains about our custom format options: */
  __attribute__ ((__format__ (__printf__, 3, 4)))
#endif
  ;

/**
   A fsl_appendf_f() impl which requires that state be an opened,
   writable (FILE*) handle.
*/
FSL_EXPORT fsl_int_t fsl_appendf_f_FILE( void * state, char const * s,
                                         fsl_int_t n );


/**
   Emulates fprintf() using fsl_appendf(). Returns the result of
   passing the data through fsl_appendf() to the given file handle.
*/
FSL_EXPORT fsl_int_t fsl_fprintf( FILE * fp, char const * fmt, ... );

/**
   The va_list counterpart of fsl_fprintf().
*/
FSL_EXPORT fsl_int_t fsl_fprintfv( FILE * fp, char const * fmt, va_list args );


/**
   Possibly reallocates self->list, changing its size. This function
   ensures that self->list has at least n entries. If n is 0 then
   the list is deallocated (but the self object is not), BUT THIS
   DOES NOT DO ANY TYPE-SPECIFIC CLEANUP of the items. If n is less
   than or equal to self->capacity then there are no side effects. If
   n is greater than self->capacity, self->list is reallocated and
   self->capacity is adjusted to be at least n (it might be bigger -
   this function may pre-allocate a larger value).

   Passing an n of 0 when self->capacity is 0 is a no-op.

   Newly-allocated slots will be initialized with NULL pointers.

   Returns 0 on success, FSL_RC_MISUSE if !self, FSL_RC_OOM if
   reservation of new elements fails.

   The return value should be used like this:

   @code
   fsl_size_t const n = number of bytes to allocate;
   int const rc = fsl_list_reserve( myList, n );
   if( rc ) { ... error ... }
   @endcode

   @see fsl_list_clear()
   @see fsl_list_visit_free()
*/
FSL_EXPORT int fsl_list_reserve( fsl_list * self, fsl_size_t n );

/**
   Appends a bitwise copy of cp to self->list, expanding the list as
   necessary and adjusting self->used.

   Ownership of cp is unchanged by this call. cp may not be NULL.

   Returns 0 on success, FSL_RC_MISUSE if any argument is NULL, or
   FSL_RC_OOM on allocation error.
*/
FSL_EXPORT int fsl_list_append( fsl_list * self, void * cp );

/**
   Swaps all contents of both lhs and rhs. Results are undefined if
   lhs or rhs are NULL or not properly initialized (via initial copy
   initialization from fsl_list_empty resp. fsl_list_empty_m).
*/
FSL_EXPORT void fsl_list_swap( fsl_list * lhs, fsl_list * rhs );

/** @typedef typedef int (*fsl_list_visitor_f)(void * p, void * visitorState )

    Generic visitor interface for fsl_list lists.  Used by
    fsl_list_visit(). p is the pointer held by that list entry and
    visitorState is the 4th argument passed to fsl_list_visit().

    Implementations must return 0 on success. Any other value causes
    looping to stop and that value to be returned, but interpration
    of the value is up to the caller (it might or might not be an
    error, depending on the context). Note that client code may use
    custom values, and is not strictly required to use FSL_RC_xxx
    values. HOWEVER...  all of the libfossil APIs which take these
    as arguments may respond differently to some codes (most notable
    FSL_RC_BREAK, which they tend to treat as a
    stop-iteration-without-error result), so clients are strongly
    encourage to return an FSL_RC_xxx value on error.
*/
typedef int (*fsl_list_visitor_f)(void * obj, void * visitorState );

/**
   A fsl_list_visitor_f() implementation which requires that obj be
   arbitrary memory which can legally be passed to fsl_free()
   (which this function does). The visitorState parameter is
   ignored.
*/
FSL_EXPORT int fsl_list_v_fsl_free(void * obj, void * visitorState );


/**
   For each item in self->list, visitor(item,visitorState) is
   called.  The item is owned by self. The visitor function MUST
   NOT free the item (unless the visitor is a finalizer!), but may
   manipulate its contents if application rules do not specify
   otherwise.

   If order is 0 or greater then the list is traversed from start
   to finish, else it is traverse from end to begin.

   Returns 0 on success, non-0 on error.

   If visitor() returns non-0 then looping stops and that code is
   returned.
*/
FSL_EXPORT int fsl_list_visit( fsl_list const * self, int order,
                               fsl_list_visitor_f visitor, void * visitorState );

/**
   A list clean-up routine which takes a callback to clean up its
   contents.

   Passes each element in the given list to
   childFinalizer(item,finalizerState). If that returns non-0,
   processing stops and that value is returned, otherwise
   fsl_list_reserve(list,0) is called and 0 is returned.

   WARNING: if cleanup fails because childFinalizer() returns non-0,
   the returned object is effectively left in an undefined state and
   the client has no way (unless the finalizer somehow accounts for it)
   to know which entries in the list were cleaned up. Thus it is highly
   recommended that finalizer functions follow the conventional wisdom
   of "destructors do not throw."

   @see fsl_list_visit_free()
*/
FSL_EXPORT int fsl_list_clear( fsl_list * list, fsl_list_visitor_f childFinalizer,
                               void * finalizerState );
/**
   Similar to fsl_list_clear(list, fsl_list_v_fsl_free, NULL), but
   only frees list->list if the second argument is true, otherwise
   it sets the list's length to 0 but keep the list->list memory
   intact for later use. Note that this function never frees the
   list argument, only its contents.

   Be sure only to use this on lists of types for which fsl_free()
   is legal. i.e. don't use it on a list of fsl_deck objects or
   other types which have their own finalizers.

   Results are undefined if list is NULL.

   @see fsl_list_clear()
*/
FSL_EXPORT void fsl_list_visit_free( fsl_list * list, bool freeListMem );

/**
   Works similarly to the visit operation without the _p suffix
   except that the pointer the visitor function gets is a (**)
   pointing back to the entry within this list. That means that
   callers can assign the entry in the list to another value during
   the traversal process (e.g. set it to 0). If shiftIfNulled is
   true then if the callback sets the list's value to 0 then it is
   removed from the list and self->used is adjusted (self->capacity
   is not changed).
*/
FSL_EXPORT int fsl_list_visit_p( fsl_list * self, int order, bool shiftIfNulled,
                                 fsl_list_visitor_f visitor, void * visitorState );


/**
   Sorts the given list using the given comparison function. Neither
   argument may be NULL. The arugments passed to the comparison function
   will be pointers to pointers to the original entries, and may (depending
   on how the list is used) point to NULL.
*/
FSL_EXPORT void fsl_list_sort( fsl_list * li, fsl_generic_cmp_f cmp);

/**
   Searches for a value in the given list, using the given
   comparison function to determine equivalence. The comparison
   function will never be passed a NULL value by this function - if
   value is NULL then only a NULL entry will compare equal to it.
   Results are undefined if li or cmpf are NULL.

   Returns the index in li of the entry, or a negative value if no
   match is found.
*/
FSL_EXPORT fsl_int_t fsl_list_index_of( fsl_list const * li,
                                        void const * value,
                                        fsl_generic_cmp_f cmpf);

/**
   Equivalent to fsl_list_index_of(li, key, fsl_strcmp_cmp).
*/
FSL_EXPORT fsl_int_t fsl_list_index_of_cstr( fsl_list const * li,
                                             char const * key );


/**
   Returns 0 if the given file is readable. Flags may be any values
   accepted by the access(2) resp. _waccess() system calls.
*/
FSL_EXPORT int fsl_file_access(const char *zFilename, int flags);

/**
   Computes a canonical pathname for a file or directory. Makes the
   name absolute if it is relative. Removes redundant / characters.
   Removes all /./ path elements. Converts /A/../ to just /. If the
   slash parameter is non-zero, the trailing slash, if any, is
   retained.

   If zRoot is not NULL then it is used for transforming a relative
   zOrigName into an absolute path. If zRoot is NULL fsl_getcwd()
   is used to determine the virtual root directory. If zRoot is
   empty (starts with a NUL byte) then this function effectively
   just sends zOrigName through fsl_file_simplify_name().

   Returns 0 on success, FSL_RC_MISUSE if !zOrigName or !pOut,
   FSL_RC_OOM if an allocation fails.

   pOut, if not NULL, is _appended_ to, so be sure to set pOut->used=0
   (or pass it to fsl_buffer_reuse()) before calling this to start
   writing at the beginning of a re-used buffer. On error pOut might
   conceivably be partially populated, but that is highly
   unlikely. Nonetheless, be sure to fsl_buffer_clear() it at some
   point regardless of success or failure.

   This function does no actual filesystem-level processing unless
   zRoot is NULL or empty (and then only to get the current
   directory). This does not confirm whether the resulting file
   exists, nor that it is strictly a valid filename for the current
   filesystem. It simply transforms a potentially relative path
   into an absolute one.

   Example:
   @code
   int rc;
   char const * zRoot = "/a/b/c";
   char const * zName = "../foo.bar";
   fsl_buffer buf = fsl_buffer_empty;
   rc = fsl_file_canonical_name2(zRoot, zName, &buf, 0);
   if(rc){
     fsl_buffer_clear(&buf);
     return rc;
   }
   assert(0 == fsl_strcmp( "/a/b/foo.bar, fsl_buffer_cstr(&buf)));
   fsl_buffer_clear(&buf);
   @endcode
*/
FSL_EXPORT int fsl_file_canonical_name2(const char *zRoot,
                                        const char *zOrigName,
                                        fsl_buffer *pOut, bool slash);

/**
   Equivalent to fsl_file_canonical_name2(NULL, zOrigName, pOut, slash).

   @see fsl_file_canonical_name2()
*/

FSL_EXPORT int fsl_file_canonical_name(const char *zOrigName,
                                       fsl_buffer *pOut, bool slash);

/**
   Calculates the "directory part" of zFilename and _appends_ it to
   pOut. The directory part is all parts up to the final path
   separator ('\\' or '/'). If leaveSlash is true (non-0) then the
   separator part is appended to pOut, otherwise it is not. This
   function only examines the first nLen bytes of zFilename.  If
   nLen is negative then fsl_strlen() is used to determine the
   number of bytes to examine.

   If zFilename ends with a slash then it is considered to be its
   own directory part. i.e.  the dirpart of "foo/" evaluates to
   "foo" (or "foo/" if leaveSlash is true), whereas the dirpart of
   "foo" resolves to nothing (empty - no output except a NUL
   terminator sent to pOut).

   Returns 0 on success, FSL_RC_MISUSE if !zFilename or !pOut,
   FSL_RC_RANGE if 0==nLen or !*zFilename, and FSL_RC_OOM if
   appending to pOut fails. If zFilename contains only a path
   separator and leaveSlash is false then only a NUL terminator is
   appended to pOut if it is not already NUL-terminated.

   This function does no filesystem-level validation of the the
   given path - only string evaluation.
*/
FSL_EXPORT int fsl_file_dirpart(char const * zFilename, fsl_int_t nLen,
                                fsl_buffer * pOut, bool leaveSlash);


/**
   Writes the absolute path name of the current directory to zBuf,
   which must be at least nBuf bytes long (nBuf includes the space
   for a trailing NUL terminator).

   Returns FSL_RC_RANGE if the name would be too long for nBuf,
   FSL_RC_IO if it cannot determine the current directory (e.g. a
   side effect of having removed the directory at runtime or similar
   things), and 0 on success.

   On success, if outLen is not NULL then the length of the string
   written to zBuf is assigned to *outLen. The output string is
   always NUL-terminated.

   On Windows, the name is converted from unicode to UTF8 and all '\\'
   characters are converted to '/'.  No conversions are needed on
   Unix.
*/
FSL_EXPORT int fsl_getcwd(char *zBuf, fsl_size_t nBuf, fsl_size_t * outLen);


/**
   Return true if the filename given is a valid filename
   for a file in a repository. Valid filenames follow all of the
   following rules:

   -  Does not begin with "/"
   -  Does not contain any path element named "." or ".."
   -  Does not contain "/..." (special case)
   -  Does not contain any of these characters in the path: "\"
   -  Does not end with "/".
   -  Does not contain two or more "/" characters in a row.
   -  Contains at least one character

   Invalid UTF8 characters result in a false return if bStrictUtf8 is
   true.  If bStrictUtf8 is false, invalid UTF8 characters are silently
   ignored. See https://en.wikipedia.org/wiki/UTF-8#Invalid_byte_sequences
   and https://en.wikipedia.org/wiki/Unicode (for the noncharacters).

   Fossil compatibility note: the bStrictUtf8 flag must be true
   when parsing new manifests but is false when parsing legacy
   manifests, for backwards compatibility.

   z must be NUL terminated. Results are undefined if !z.

   Note that periods in and of themselves are valid filename
   components, with the special exceptions of "." and "..", one
   implication being that "...." is, for purposes of this function,
   a valid simple filename.
*/
FSL_EXPORT bool fsl_is_simple_pathname(const char *z, bool bStrictUtf8);

/**
   Return the size of a file in bytes. Returns -1 if the file does
   not exist or is not stat(2)able.
*/
FSL_EXPORT fsl_int_t fsl_file_size(const char *zFilename);

/**
   Return the modification time for a file.  Return -1 if the file
   does not exist or is not stat(2)able.
*/
FSL_EXPORT fsl_time_t fsl_file_mtime(const char *zFilename);

#if 0
/**
   Don't use this. The wd (working directory) family of functions
   might or might-not be necessary and in any case they require
   a fsl_cx context argument because they require repo-specific
   "allow-symlinks" setting.

   Return TRUE if the named file is an ordinary file or symlink
   and symlinks are allowed.

   Return false for directories, devices, fifos, etc.
*/
FSL_EXPORT bool fsl_wd_isfile_or_link(const char *zFilename);
#endif

/**
   Returns true if the named file is an ordinary file. Returns false
   for directories, devices, fifos, symlinks, etc. The name
   may be absolute or relative to the current working dir.
*/
FSL_EXPORT bool fsl_is_file(const char *zFilename);

/**
   Returns true if the given file is a symlink, else false. The name
   may be absolute or relative to the current working dir.
*/
FSL_EXPORT bool fsl_is_symlink(const char *zFilename);

/**
   Returns true if the given filename refers to a plain file or
   symlink, else returns false. The name may be absolute or relative
   to the current working dir.
*/
FSL_EXPORT bool fsl_is_file_or_link(const char *zFilename);

/**
   Returns true if the given path appears to be absolute, else
   false. On Unix a path is absolute if it starts with a '/'.  On
   Windows a path is also absolute if it starts with a letter, a
   colon, and either a backslash or forward slash.
*/
FSL_EXPORT bool fsl_is_absolute_path(const char *zPath);

/**
   Simplify a filename by:

   * converting all \ into / on windows and cygwin
   * removing any trailing and duplicate /
   * removing /./
   * removing /A/../

   Changes are made in-place.  Return the new name length.  If the
   slash parameter is non-zero, the trailing slash, if any, is
   retained. If n is <0 then fsl_strlen() is used to calculate the
   length.
*/
FSL_EXPORT fsl_size_t fsl_file_simplify_name(char *z, fsl_int_t n_, bool slash);

/**
   Return true (non-zero) if string z matches glob pattern zGlob
   and zero if the pattern does not match. Always returns 0 if
   either argument is NULL. Supports all globbing rules
   supported by sqlite3_strglob().
*/
FSL_EXPORT bool fsl_str_glob(const char *zGlob, const char *z);

/**
   Parses zPatternList as a comma-and/or-fsl_isspace()-delimited
   list of glob patterns (as supported by fsl_str_glob()). Each
   pattern in that list is copied and appended to tgt in the form
   of a new (char *) owned by that list.

   Returns 0 on success, FSL_RC_OOM if copying a pattern to tgt
   fails, FSL_RC_MISUSE if !tgt or !zPatternList. An empty
   zPatternList is not considered an error (to simplify usage) and
   has no side-effects. On allocation error, tgt might be partially
   populated.

   Elements of the glob list may be optionally enclosed in single
   or double-quotes.  This allows a comma to be part of a glob
   pattern. 

   Leading and trailing spaces on unquoted glob patterns are
   ignored.

   Note that there is no separate "glob list" class. A "glob list"
   is simply a fsl_list whose list entries are glob-pattern strings
   owned by that list.

   Examples of a legal value for zPatternList:
   @code
   "*.c *.h, *.sh, '*.in'"
   @endcode

   @see fsl_glob_list_append()
   @see fsl_glob_list_matches()
   @see fsl_glob_list_clear()
*/
FSL_EXPORT int fsl_glob_list_parse( fsl_list * tgt, char const * zPatternList );

/**
   Appends a single blob pattern to tgt, in the form of a new (char *)
   owned by tgt. This function copies zGlob and appends that copy
   to tgt.

   Returns 0 on success, FSL_RC_MISUSE if !tgt or !zGlob or
   !*zGlob, FSL_RC_OOM if appending to the list fails.

   @see fsl_glob_list_parse()
   @see fsl_glob_list_matches()
   @see fsl_glob_list_clear()
*/
FSL_EXPORT int fsl_glob_list_append( fsl_list * tgt, char const * zGlob );

/**
   Assumes globList is a list of (char [const] *) glob values and
   tries to match each one against zHaystack using
   fsl_str_glob(). If any glob matches, it returns a pointer to the
   matched globList->list entry. If no matches are found, or if any
   argument is invalid, NULL is returned.

   The returned bytes are owned by globList and may be invalidated at
   its leisure. It is primarily intended to be used as a boolean,
   for example:

   @code
   if( fsl_glob_list_matches(myGlobs, someFilename) ) { ... }
   @endcode

   @see fsl_glob_list_parse()
   @see fsl_glob_list_append()
   @see fsl_glob_list_clear()
*/
FSL_EXPORT char const * fsl_glob_list_matches( fsl_list const * globList, char const * zHaystack );

/**
   If globList is not NULL this is equivalent to
   fsl_list_visit_free(globList, 1), otherwise it is a no-op.

   Note that this does not free the globList object itself, just
   its underlying list entries and list memory. (In practice, lists
   are either allocated on the stack or as part of a higher-level
   structure, and not on the heap.)

   @see fsl_glob_list_parse()
   @see fsl_glob_list_append()
   @see fsl_glob_list_matches()
*/
FSL_EXPORT void fsl_glob_list_clear( fsl_list * globList );


/**
   Returns true if the given letter is an ASCII alphabet character.
*/
FSL_EXPORT char fsl_isalpha(int c);

/**
   Returns true if c is a lower-case ASCII alphabet character.
*/
FSL_EXPORT char fsl_islower(int c);

/**
   Returns true if c is an upper-case ASCII alphabet character.
*/
FSL_EXPORT char fsl_isupper(int c);

/**
   Returns true if c is ' ', '\\r' (ASCII 13dec), or '\\t' (ASCII 9
   dec).
*/
FSL_EXPORT char fsl_isspace(int c);

/**
   Returns true if c is an ASCII digit in the range '0' to '9'.
*/
FSL_EXPORT char fsl_isdigit(int c);

/**
   Equivalent to fsl_isdigit(c) || fsl_isalpha().
*/
FSL_EXPORT char fsl_isalnum(int c);

/**
   Returns the upper-case form of c if c is an ASCII alphabet
   letter, else returns c.
*/
FSL_EXPORT int fsl_tolower(int c);

/**
   Returns the lower-case form of c if c is an ASCII alphabet
   letter, else returns c.
*/
FSL_EXPORT int fsl_toupper(int c);

#ifdef _WIN32
/**
   Translate MBCS to UTF-8.  Return a pointer to the translated
   text. ACHTUNG: Call fsl_mbcs_free() (not fsl_free()) to
   deallocate any memory used to store the returned pointer when
   done.
*/
FSL_EXPORT char * fsl_mbcs_to_utf8(char const * mbcs);

/**
   Frees a string allocated from fsl_mbcs_to_utf8(). Results are undefined
   if mbcs was allocated using any other mechanism.
*/
FSL_EXPORT void fsl_mbcs_free(char * mbcs);
#endif
/* _WIN32 */

/**
   Deallocates the given memory, which must have been allocated
   from fsl_unicode_to_utf8(), fsl_utf8_to_unicode(), or any
   function which explicitly documents this function as being the
   proper finalizer for its returned memory.
*/
FSL_EXPORT void fsl_unicode_free(void *);


/**
   Translate UTF-8 to Unicode for use in system calls. Returns a
   pointer to the translated text. The returned value must
   eventually be passed to fsl_unicode_free() to deallocate any
   memory used to store the returned pointer when done.

   This function exists only for Windows. On other platforms
   it behaves like fsl_strdup().

   The returned type is (wchar_t*) on Windows and (char*)
   everywhere else.
*/
FSL_EXPORT void *fsl_utf8_to_unicode(const char *zUtf8);

/**
   Translates Unicode text into UTF-8.  Return a pointer to the
   translated text. Call fsl_unicode_free() to deallocate any
   memory used to store the returned pointer when done.

   This function exists only for Windows. On other platforms it
   behaves like fsl_strdup().
*/
FSL_EXPORT char *fsl_unicode_to_utf8(const void *zUnicode);

/**
   Translate text from the OS's character set into UTF-8. Return a
   pointer to the translated text. Call fsl_filename_free() to
   deallocate any memory used to store the returned pointer when
   done.

   This function must not convert '\' to '/' on Windows/Cygwin, as
   it is used in places where we are not sure it's really filenames
   we are handling, e.g. fsl_getenv() or handling the argv
   arguments from main().

   On Windows, translate some characters in the in the range
   U+F001 - U+F07F (private use area) to ASCII. Cygwin sometimes
   generates such filenames. See:
   <https://cygwin.com/cygwin-ug-net/using-specialnames.html>
*/
FSL_EXPORT char *fsl_filename_to_utf8(const void *zFilename);

/**
   Translate text from UTF-8 to the OS's filename character set.
   Return a pointer to the translated text. Call
   fsl_filename_free() to deallocate any memory used to store the
   returned pointer when done.

   On Windows, characters in the range U+0001 to U+0031 and the
   characters '"', '*', ':', '<', '>', '?' and '|' are invalid in
   filenames. Therefore, translate those to characters in the in the
   range U+F001 - U+F07F (private use area), so those characters
   never arrive in any Windows API. The filenames might look
   strange in Windows explorer, but in the cygwin shell everything
   looks as expected.

   See: <https://cygwin.com/cygwin-ug-net/using-specialnames.html>

   The returned type is (wchar_t*) on Windows and (char*)
   everywhere else.
*/
FSL_EXPORT void *fsl_utf8_to_filename(const char *zUtf8);


/**
   Deallocate pOld, which must have been allocated by
   fsl_filename_to_utf8(), fsl_utf8_to_filename(), fsl_getenv(), or
   another routine which explicitly documents this function as
   being the proper finalizer for its returned memory.
*/
FSL_EXPORT void fsl_filename_free(void *pOld);

/**
   Returns a (possible) copy of the environment variable with the
   given key, or NULL if no entry is found. The returned value must
   be passed to fsl_filename_free() to free it. ACHTUNG: DO NOT
   MODIFY the returned value - on Unix systems it is _not_ a
   copy. That interal API inconsistency "should" be resolved
   (==return a copy from here, but that means doing it everywhere)
   to avoid memory ownership problems later on.

   Why return a copy? Because native strings from at least one of
   the more widespread OSes often have to be converted to something
   portable and this requires allocation on such platforms, but
   not on Unix. For API transparency, that means all platforms get
   the copy(-like) behaviour.
*/
FSL_EXPORT char *fsl_getenv(const char *zName);

/**
   Returns a positive value if zFilename is a directory, 0 if
   zFilename does not exist, or a negative value if zFilename
   exists but is something other than a directory. Results are
   undefined if zFilename is NULL.

   This function expects zFilename to be a well-formed path -
   it performs no normalization on it.
*/
FSL_EXPORT int fsl_dir_check(const char *zFilename);

/**
   Check the given path to determine whether it is an empty directory.
   Returns 0 on success (i.e., directory is empty), <0 if the provided
   path is not a directory or cannot be opened, and >0 if the
   directory is not empty.
*/
FSL_EXPORT int fsl_dir_is_empty(const char *path);

/**
   Deletes the given file from the filesystem. Returns 0 on
   success. If the component is a directory, this operation will
   fail. If zFilename refers to a symlink, the link (not its target)
   is removed.

   Results are undefined if zFilename is NULL.

   Potential TODO: if it refers to a dir, forward the call to
   fsl_rmdir().
*/
FSL_EXPORT int fsl_file_unlink(const char *zFilename);


/**
   Renames zFrom to zTo, as per rename(3). Returns 0 on success.  On
   error it returns FSL_RC_OOM on allocation error when converting the
   names to platforms-specific character sets (on platforms where that
   happens) or an FSL_RC_xxx value approximating an errno value, as
   per fsl_errno_to_rc().
*/
FSL_EXPORT int fsl_file_rename(const char *zFrom, const char *zTo);

/**
   Deletes an empty directory from the filesystem. Returns 0
   on success. There are any number of reasons why deletion
   of a directory can fail, some of which include:

   - It is not empty or permission denied (FSL_RC_ACCESS).

   - Not found (FSL_RC_NOT_FOUND).

   - Is not a directory or (on Windows) is a weird pseudo-dir type for
     which rmdir() does not work (FSL_RC_TYPE).

   - I/O error (FSL_RC_IO).

   @see fsl_dir_is_empty()
*/
FSL_EXPORT int fsl_rmdir(const char *zFilename);

/**
   Sets the mtime (Unix epoch) for a file. Returns 0 on success,
   non-0 on error. If newMTime is less than 0 then the current
   time(2) is used. This routine does not create non-existent files
   (e.g. like a Unix "touch" command).
*/
FSL_EXPORT int fsl_file_mtime_set(const char *zFilename, fsl_time_t newMTime);

/**
   On non-Windows platforms, this function sets or unsets the
   executable bits on the given filename. All other permissions are
   retained as-is. Returns 0 on success. On Windows this is a no-op,
   returning 0.

   If the target is a directory or a symlink, this is a no-op and
   returns 0.
*/
FSL_EXPORT int fsl_file_exec_set(const char *zFilename, bool isExec);


/**
   Create the directory with the given name if it does not already
   exist. If forceFlag is true, delete any prior non-directory
   object with the same name.

   Return 0 on success, non-0 on error.

   If the directory already exists then 0 is returned, not an error
   (FSL_RC_ALREADY_EXISTS), because that simplifies usage. If
   another filesystem entry with this name exists and forceFlag is
   true then that entry is deleted before creating the directory,
   and this operation fails if deletion fails. If forceFlag is
   false and a non-directory entry already exists, FSL_RC_TYPE is
   returned.

   For recursively creating directories, use fsl_mkdir_for_file().

   Bug/corner case: if zFilename refers to a symlink to a
   non-existent directory, this function gets slightly confused,
   tries to make a dir with the symlink's name, and returns
   FSL_RC_ALREADY_EXISTS. How best to resolve that is not yet
   clear. The problem is that stat(2)ing the symlink says "nothing
   is there" (because the link points to a non-existing thing), so
   we move on to the underlying mkdir(), which then fails because
   the link exists with that name.
*/
FSL_EXPORT int fsl_mkdir(const char *zName, bool forceFlag);

/**
   A convenience form of fsl_mkdir() which can recursively create
   directories. If zName has a trailing slash then the last
   component is assumed to be a directory part, otherwise it is
   assumed to be a file part (and no directory is created for that
   part). zName may be either an absolute or relative path.

   Returns 0 on success (including if all directories already exist).
   Returns FSL_RC_OOM if there is an allocation error. Returns
   FSL_RC_TYPE if one of the path components already exists and is not
   a directory. Returns FSL_RC_RANGE if zName is NULL or empty. If
   zName is only 1 byte long, this is a no-op.

   On systems which support symlinks, a link to a directory is
   considered to be a directory for purposes of this function.

   If forceFlag is true and a non-directory component is found in
   the filesystem where zName calls for a directory, that component
   is removed (and this function fails if removal fails).

   Examples:

   "/foo/bar" creates (if needed) /foo, but assumes "bar" is a file
   component. "/foo/bar/" creates /foo/bar. However "foo" will not
   create a directory - because the string has no path component,
   it is assumed to be a filename.

   Both "/foo/bar/my.doc" and "/foo/bar/" result in the directories
   /foo/bar.

*/
FSL_EXPORT int fsl_mkdir_for_file(char const *zName, bool forceFlag);


/**
   Uses fsl_getenv() to look for the environment variables
   (FOSSIL_USER, (Windows: USERNAME), (Unix: USER, LOGNAME)). If
   it finds one it returns a copy of that value, which must
   eventually be passed to fsl_free() to free it (NOT
   fsl_filename_free(), though fsl_getenv() requires that one). If
   it finds no match, or if copying the entry fails, it returns
   NULL.

   @see fsl_cx_user_set()
   @see fsl_cx_user_get()
*/
FSL_EXPORT char * fsl_guess_user_name();

/**
   Tries to find the user's home directory. If found, 0 is
   returned, tgt's memory is _overwritten_ (not appended) with the
   path, and tgt->used is set to the path's string length.  (Design
   note: the overwrite behaviour is inconsistent with most of the
   API, but the implementation currently requires this.)

   If requireWriteAccess is true then the directory is checked for
   write access, and FSL_RC_ACCESS is returned if that check
   fails. For historical (possibly techinical?) reasons, this check
   is only performed on Unix platforms. On others this argument is
   ignored. When writing code on Windows, it may be necessary to
   assume that write access is necessary on non-Windows platform,
   and to pass 1 for the second argument even though it is ignored
   on Windows.

   On error non-0 is returned and tgt is updated with an error
   string OR (if the error was an allocation error while appending
   to the path or allocating MBCS strings for Windows), it returns
   FSL_RC_OOM and tgt "might" be updated with a partial path (up to
   the allocation error), and "might" be empty (if the allocation
   error happens early on).

   This routine does not canonicalize/transform the home directory
   path provided by the environment, other than to convert the
   string byte encoding on some platforms. i.e. if the environment
   says that the home directory is "../" then this function will
   return that value, possibly to the eventual disappointment of
   the caller.

   Result codes include:

   - FSL_RC_OK (0) means a home directory was found and tgt is
   populated with its path.

   - FSL_RC_NOT_FOUND means the home directory (platform-specific)
   could not be found.

   - FSL_RC_ACCESS if the home directory is not writable and
   requireWriteAccess is true. Unix platforms only -
   requireWriteAccess is ignored on others.

   - FSL_RC_TYPE if the home (as determined via inspection of the
   environment) is not a directory.

   - FSL_RC_OOM if a memory (re)allocation fails.
*/
FSL_EXPORT int fsl_find_home_dir( fsl_buffer * tgt, bool requireWriteAccess );

/**
   Values for use with the fsl_fstat::type field.
*/
enum fsl_fstat_type_e {
/** Sentinel value for unknown/invalid filesystem entry types. */
FSL_FSTAT_TYPE_UNKNOWN = 0,
/** Indicates a directory filesystem entry. */
FSL_FSTAT_TYPE_DIR,
/** Indicates a non-directory, non-symlink filesystem entry.
    Because fossil's scope is limited to SCM work, it assumes that
    "special files" (sockets, etc.) are just files, and makes no
    special effort to handle them.
*/
FSL_FSTAT_TYPE_FILE,
/** Indicates a symlink filesystem entry. */
FSL_FSTAT_TYPE_LINK
};
typedef enum fsl_fstat_type_e fsl_fstat_type_e;

/**
   Bitmask values for use with the fsl_fstat::perms field.

   Only permissions which are relevant for fossil are listed here.
   e.g. read-vs-write modes are irrelevant for fossil as it does not
   track them. It manages only the is-executable bit. In in the
   contexts of fossil manifests, it also treats "is a symlink" as a
   permission flag.
*/
enum fsl_fstat_perm_e {
/**
   Sentinel value.
*/
FSL_FSTAT_PERM_UNKNOWN = 0,
/**
   The executable bit, as understood by Fossil. Fossil does not
   differentiate between different +x values for user/group/other.
*/
FSL_FSTAT_PERM_EXE = 0x01
};
typedef enum fsl_fstat_perm_e fsl_fstat_perm_e;

/**
   A simple wrapper around the stat(2) structure resp. _stat/_wstat
   (on Windows). It exposes only the aspects of stat(2) info which
   Fossil works with, and not any platform-/filesystem-specific
   details except the executable bit for the permissions mode and some
   handling of symlinks.
*/
struct fsl_fstat {
  /**
     Indicates the type of filesystem object.
  */
  fsl_fstat_type_e type;
  /**
     The time of the last file metadata change (owner, permissions,
     etc.). The man pages (neither for Linux nor Windows) do not
     specify exactly what unit this is. Let's assume seconds since the
     start of the Unix Epoch.
  */
  fsl_time_t ctime;
  /**
     Last modification time.
  */
  fsl_time_t mtime;
  /**
     The size of the stat'd file, in bytes.
  */
  fsl_size_t size;
  /**
     Contains the filesystem entry's permissions as a bitmask of
     fsl_fstat_perm_e values. Note that only the executable bit for
     _files_ (not directories) is exposed here.
  */
  int perm;
};

/** Empty-initialized fsl_fstat structure, intended for const-copy
    construction. */
#define fsl_fstat_empty_m {FSL_FSTAT_TYPE_UNKNOWN,0,0,-1,0}

/** Empty-initialized fsl_fstat instance, intended for non-const copy
    construction. */
FSL_EXPORT const fsl_fstat fsl_fstat_empty;

/**
   Runs the OS's stat(2) equivalent to populate fst with
   information about the given file.

   Returns 0 on success, FSL_RC_MISUSE if zFilename is NULL, and
   FSL_RC_RANGE if zFilename starts with a NUL byte. Returns
   FSL_RC_NOT_FOUND if no filesystem entry is found for the given
   name. Returns FSL_RC_IO if the underlying stat() (or equivalent)
   fails for undetermined reasons inside the underlying
   stat()/_wstati64() call. Note that the fst parameter may be
   NULL, in which case the return value will be 0 if the name is
   stat-able, but will return no other information about it.

   The derefSymlinks argument is ignored on non-Unix platforms.  On
   Unix platforms, if derefSymlinks is true then stat(2) is used, else
   lstat(2) (if available on the platform) is used. For most cases
   clients should pass true. They should only pass false if they need
   to differentiate between symlinks and files.

   The fsl_fstat_type_e family of flags can be used to determine the
   type of the filesystem object being stat()'d (file, directory, or
   symlink). It does not apply any special logic for platform-specific
   oddities other than symlinks (e.g. character devices and such).
*/
FSL_EXPORT int fsl_stat(const char *zFilename, fsl_fstat * fst,
                        bool derefSymlinks);

/**
   Create a new delta between the memory zIn and zOut.

   The delta is written into a preallocated buffer, zDelta, which
   must be at least 60 bytes longer than the target memory, zOut.
   The delta string will be NUL-terminated, but it might also
   contain embedded NUL characters if either the zSrc or zOut files
   are binary.

   On success this function returns 0 and the length of the delta
   string, in bytes, excluding the final NUL terminator character,
   is written to *deltaSize.

   Returns FSL_RC_MISUSE if any of the pointer arguments are NULL
   and FSL_RC_OOM if memory allocation fails during generation of
   the delta. Returns FSL_RC_RANGE if lenSrc or lenOut are "too
   big" (if they cause an overflow in the math).

   Output Format:

   The delta begins with a base64 number followed by a newline.
   This number is the number of bytes in the TARGET file.  Thus,
   given a delta file z, a program can compute the size of the
   output file simply by reading the first line and decoding the
   base-64 number found there.  The fsl_delta_applied_size()
   routine does exactly this.

   After the initial size number, the delta consists of a series of
   literal text segments and commands to copy from the SOURCE file.
   A copy command looks like this:

   (Achtung: extra backslashes are for Doxygen's benefit - not
   visible in the processsed docs.)

   NNN\@MMM,

   where NNN is the number of bytes to be copied and MMM is the
   offset into the source file of the first byte (both base-64).
   If NNN is 0 it means copy the rest of the input file.  Literal
   text is like this:

   NNN:TTTTT

   where NNN is the number of bytes of text (base-64) and TTTTT is
   the text.

   The last term is of the form

   NNN;

   In this case, NNN is a 32-bit bigendian checksum of the output
   file that can be used to verify that the delta applied
   correctly.  All numbers are in base-64.

   Pure text files generate a pure text delta.  Binary files
   generate a delta that may contain some binary data.

   Algorithm:

   The encoder first builds a hash table to help it find matching
   patterns in the source file.  16-byte chunks of the source file
   sampled at evenly spaced intervals are used to populate the hash
   table.

   Next we begin scanning the target file using a sliding 16-byte
   window.  The hash of the 16-byte window in the target is used to
   search for a matching section in the source file.  When a match
   is found, a copy command is added to the delta.  An effort is
   made to extend the matching section to regions that come before
   and after the 16-byte hash window.  A copy command is only
   issued if the result would use less space that just quoting the
   text literally. Literal text is added to the delta for sections
   that do not match or which can not be encoded efficiently using
   copy commands.

   @see fsl_delta_applied_size()
   @see fsl_delta_apply()
*/
FSL_EXPORT int fsl_delta_create( unsigned char const *zSrc, fsl_size_t lenSrc,
                      unsigned char const *zOut, fsl_size_t lenOut,
                      unsigned char *zDelta, fsl_size_t * deltaSize);

/**
   Works identically to fsl_delta_create() but sends its output to
   the given output function. out(outState,...) may be called any
   number of times to emit delta output. Each time it is called it
   should append the new bytes to its output channel.

   The semantics of the return value and the first four arguments
   are identical to fsl_delta_create(), with these ammendments
   regarding the return value:

   - Returns FSL_RC_MISUSE if any of (zSrc, zOut, out) are NULL.

   - If out() returns non-0 at any time, delta generation is
   aborted and that code is returned.

   Example usage:

   @code
   int rc = fsl_delta_create( v1, v1len, v2, v2len,
   fsl_output_f_FILE, stdout);
   @endcode
*/
FSL_EXPORT int fsl_delta_create2( unsigned char const *zSrc, fsl_size_t lenSrc,
                       unsigned char const *zOut, fsl_size_t lenOut,
                       fsl_output_f out, void * outState);

/**
   A fsl_delta_create() wrapper which uses the first two arguments
   as the original and "new" content versions to delta, and outputs
   the delta to the 3rd argument (overwriting any existing contents
   and re-using any memory it had allocated).

   If the output buffer (delta) is the same as src or newVers,
   FSL_RC_MISUSE is returned, and results are undefined if delta
   indirectly refers to the same buffer as either src or newVers.

   Returns 0 on success.
*/
FSL_EXPORT int fsl_buffer_delta_create( fsl_buffer const * src,
                             fsl_buffer const * newVers,
                             fsl_buffer * delta);

/**
   Apply a delta created using fsl_delta_create().

   The output buffer must be big enough to hold the whole output
   file and a NUL terminator at the end. The
   fsl_delta_applied_size() routine can be used to determine that
   size.

   zSrc represents the original sources to apply the delta to.
   It must be at least lenSrc bytes of valid memory.

   zDelta holds the delta (created using fsl_delta_create()),
   and it must be lenDelta bytes long.

   On success this function returns 0 and writes the applied delta
   to zOut.

   Returns FSL_RC_MISUSE if any pointer argument is NULL. Returns
   FSL_RC_RANGE if lenSrc or lenDelta are "too big" (if they cause
   an overflow in the math). Invalid delta input can cause any of
   FSL_RC_RANGE, FSL_RC_DELTA_INVALID_TERMINATOR,
   FSL_RC_CHECKSUM_MISMATCH, FSL_RC_SIZE_MISMATCH, or
   FSL_RC_DELTA_INVALID_OPERATOR to be returned.

   Refer to the fsl_delta_create() documentation above for a
   description of the delta file format.

   @see fsl_delta_applied_size()
   @see fsl_delta_create()
   @see fsl_delta_apply2()
*/
FSL_EXPORT int fsl_delta_apply( unsigned char const *zSrc, fsl_size_t lenSrc,
                     unsigned char const *zDelta, fsl_size_t lenDelta,
                     unsigned char *zOut );

/**
   Functionally identical to fsl_delta_apply() but any errors generated
   during application of the delta are described in more detail
   in pErr. If pErr is NULL this behaves exactly as documented for
   fsl_delta_apply().
*/
FSL_EXPORT int fsl_delta_apply2( unsigned char const *zSrc,
                      fsl_size_t lenSrc,
                      unsigned char const *zDelta,
                      fsl_size_t lenDelta,
                      unsigned char *zOut,
                      fsl_error * pErr);
/*
  Calculates the size (in bytes) of the output from applying a the
  given delta. On success 0 is returned and *appliedSize will be
  updated with the amount of memory required for applying the
  delta. zDelta must point to lenDelta bytes of memory in the
  format emitted by fsl_delta_create(). It is legal for appliedSize
  to point to the same memory as the 2nd argument.

  Returns FSL_RC_MISUSE if any pointer argument is NULL. Returns
  FSL_RC_RANGE if lenDelta is too short to be a delta. Returns
  FSL_RC_DELTA_INVALID_TERMINATOR if the delta's encoded length
  is not properly terminated.

  This routine is provided so that an procedure that is able to
  call fsl_delta_apply() can learn how much space is required for
  the output and hence allocate nor more space that is really
  needed.

  TODO?: consolidate 2nd and 3rd parameters into one i/o parameter?

  @see fsl_delta_apply()
  @see fsl_delta_create()
*/
FSL_EXPORT int fsl_delta_applied_size(unsigned char const *zDelta,
                           fsl_size_t lenDelta,
                           fsl_size_t * appliedSize);

/**
   "Fossilizes" the first len bytes of the given input string. If
   (len<0) then fsl_strlen(inp) is used to calculate its length.
   The output is appended to out, which is expanded as needed and
   out->used is updated accordingly.  Returns 0 on success,
   FSL_RC_MISUSE if !inp or !out. Returns 0 without side-effects if
   0==len or (!*inp && len<0). Returns FSL_RC_OOM if reservation of
   the output buffer fails (it is expanded, at most, one time by
   this function).

   Fossilization replaces the following bytes/sequences with the
   listed replacements:

   (Achtung: usage of doubled backslashes here it just to please
   doxygen - they will show up as single slashes in the processed
   output.)

   - Backslashes are doubled.

   - (\\n, \\r, \\v, \\t, \\f) are replaced with \\\\X, where X is the
   conventional encoding letter for that escape sequence.

   - Spaces are replaced with \\s.

   - Embedded NULs are replaced by \\0 (numeric 0, not character
   '0').
*/
FSL_EXPORT int fsl_bytes_fossilize( unsigned char const * inp, fsl_int_t len,
                         fsl_buffer * out );
/**
   "Defossilizes" bytes encoded by fsl_bytes_fossilize() in-place.
   inp must be a string encoded by fsl_bytes_fossilize(), and the
   decoding processes stops at the first unescaped NUL terminator.
   It has no error conditions except for !inp or if inp is not
   NUL-terminated, both of which invoke in undefined behaviour.

   If resultLen is not NULL then *resultLen is set to the resulting string
   length.

*/
FSL_EXPORT void fsl_bytes_defossilize( unsigned char * inp, fsl_size_t * resultLen );

/**
   Defossilizes the contents of b. Equivalent to:
   fsl_bytes_defossilize( b->mem, &b->used );
*/
FSL_EXPORT void fsl_buffer_defossilize( fsl_buffer * b );

/**
   Returns true if the input string contains only valid lower-case
   base-16 digits. If any invalid characters appear in the string,
   false is returned.
*/
FSL_EXPORT bool fsl_validate16(const char *zIn, fsl_size_t nIn);

/**
   The input string is a base16 value.  Convert it into its canonical
   form.  This means that digits are all lower case and that conversions
   like "l"->"1" and "O"->"0" occur.
*/
FSL_EXPORT void fsl_canonical16(char *z, fsl_size_t n);

/**
   Decode a N-character base-16 number into base-256.  N must be a 
   multiple of 2. The output buffer must be at least N/2 characters
   in length. Returns 0 on success.
*/
FSL_EXPORT int fsl_decode16(const unsigned char *zIn, unsigned char *pOut, fsl_size_t N);

/**
   Encode a N-digit base-256 in base-16. N is the byte length of pIn
   and zOut must be at least (N*2+1) bytes long (the extra is for a
   terminating NUL). Returns zero on success, FSL_RC_MISUSE if !pIn
   or !zOut.
*/
FSL_EXPORT int fsl_encode16(const unsigned char *pIn, unsigned char *zOut, fsl_size_t N);


/**
   Tries to convert the value of errNo, which is assumed to come
   from the global errno, to a fsl_rc_e code. If it can, it returns
   something approximating the errno value, else it returns dflt.

   Example usage:

   @code
   FILE * f = fsl_fopen("...", "...");
   int rc = f ? 0 : fsl_errno_to_rc(errno, FSL_RC_IO);
   ...
   @endcode

   Why require the caller to pass in errno, instead of accessing it
   directly from this function? To avoid the the off-chance that
   something changes errno between the call and the conversion
   (whether or not that's possible is as yet undetermined). It can
   also be used by clients to map to explicit errno values to
   fsl_rc_e values, e.g. fsl_errno_to_rc(EROFS,-1) returns
   FSL_RC_ACCESS.

   A list of the errno-to-fossil conversions:

   - EINVAL: FSL_RC_MISUSE (could arguably be FSL_RC_RANGE, though)

   - ENOMEM: FSL_RC_OOM

   - EACCES, EBUSY, EPERM: FSL_RC_ACCESS

   - EISDIR, ENOTDIR: FSL_RC_TYPE

   - ENAMETOOLONG, ELOOP: FSL_RC_RANGE

   - ENOENT: FSL_RC_NOT_FOUND

   - EEXIST: FSL_RC_ALREADY_EXISTS

   - EIO: FSL_RC_IO

   Any other value for errNo causes dflt to be returned.

*/
FSL_EXPORT int fsl_errno_to_rc(int errNo, int dflt);

/**
   Make the given string safe for HTML by converting every "<" into
   "&lt;", every ">" into "&gt;", every "&" into "&amp;", and
   encode '"' as &quot; so that it can appear as an argument to
   markup.

   The escaped output is send to out(oState,...).

   Returns 0 on success or if there is nothing to do (input has a
   length of 0, in which case out() is not called). Returns
   FSL_RC_MISUSE if !out or !zIn. If out() returns a non-0 code
   then that value is returned to the caller.

   If n is negative, fsl_strlen() is used to calculate zIn's length.
*/
FSL_EXPORT int fsl_htmlize(fsl_output_f out, void * oState,
                const char *zIn, fsl_int_t n);

/**
   Functionally equivalent to fsl_htmlize() but optimized to perform
   only a single allocation.

   Returns 0 on success or if there is nothing to do (input has a
   length of 0). Returns FSL_RC_MISUSE if !p or !zIn, and
   FSL_RC_OOM on allocation error.

   If n is negative, fsl_strlen() is used to calculate zIn's length.
*/
FSL_EXPORT int fsl_htmlize_to_buffer(fsl_buffer *p, const char *zIn, fsl_int_t n);

/**
   Equivalent to fsl_htmlize_to_buffer() but returns the result as a
   new string which must eventually be fsl_free()d by the caller.

   Returns NULL for invalidate arguments or allocation error.
*/
FSL_EXPORT char *fsl_htmlize_str(const char *zIn, fsl_int_t n);


/**
   If c is a character Fossil likes to HTML-escape, assigns *xlate
   to its transformed form, else set it to NULL. Returns 1 for
   untransformed characters and the strlen of *xlate for others.
   Bytes returned via xlate are static and immutable.

   Results are undefined if xlate is NULL.
*/
FSL_EXPORT fsl_size_t fsl_htmlize_xlate(int c, char const ** xlate);

/**
   Flags for use with text-diff generation APIs,
   e.g. fsl_diff_text().

   Maintenance reminder: these values are holy and must not be
   changed without also changing the corresponding code in
   diff.c.
*/
enum fsl_diff_flag_t {
/** Ignore end-of-line whitespace */
FSL_DIFF_IGNORE_EOLWS = 0x01,
/** Ignore end-of-line whitespace */
FSL_DIFF_IGNORE_ALLWS = 0x03,
/** Generate a side-by-side diff */
FSL_DIFF_SIDEBYSIDE =   0x04,
/** Missing shown as empty files */
FSL_DIFF_VERBOSE =      0x08,
/** Show filenames only. Not used in this impl! */
FSL_DIFF_BRIEF =        0x10,
/** Render HTML. */
FSL_DIFF_HTML =         0x20,
/** Show line numbers. */
FSL_DIFF_LINENO =       0x40,
/** Suppress optimizations (debug). */
FSL_DIFF_NOOPT =        0x0100,
/** Invert the diff (debug). */
FSL_DIFF_INVERT =       0x0200,
/** Only display if not "too big." */
FSL_DIFF_NOTTOOBIG =    0x0800,
/** Strip trailing CR */
FSL_DIFF_STRIP_EOLCR =    0x1000,
/**
   This flag tells text-mode diff generation to add ANSI color
   sequences to some output.  The colors are currently hard-coded
   and non-configurable. This has no effect for HTML output, and
   that flag trumps this one. It also currently only affects
   unified diffs, not side-by-side.

   Maintenance reminder: this one currently has no counterpart in
   fossil(1), is not tracked in the same way, and need not map to an
   internal flag value. That's a good thing, because we'll be out
   of flags once Jan's done tinkering in fossil(1) ;).
*/
FSL_DIFF_ANSI_COLOR =     0x2000
};

/**
   Generates a textual diff from two text inputs and writes
   it to the given output function.

   pA and pB are the buffers to diff.

   contextLines is the number of lines of context to output. This
   parameter has a built-in limit of 2^16, and values larger than
   that get truncated. A value of 0 is legal, in which case no
   surrounding context is provided. A negative value translates to
   some unspecified default value.

   sbsWidth specifies the width (in characters) of the side-by-side
   columns. If sbsWidth is not 0 then this function behaves as if
   diffFlags contains the FSL_DIFF_SIDEBYSIDE flag. If sbsWidth is
   negative, OR if diffFlags explicitly contains
   FSL_DIFF_SIDEBYSIDE and sbsWidth is 0, then some default width
   is used. This parameter has a built-in limit of 255, and values
   larger than that get truncated to 255.

   diffFlags is a mask of fsl_diff_flag_t values. Not all of the
   fsl_diff_flag_t flags are yet [sup]ported.

   The output is sent to out(outState,...). If out() returns non-0
   during processing, processing stops and that result is returned
   to the caller of this function.

   Returns 0 on success, FSL_RC_OOM on allocation error,
   FSL_RC_MISUSE if any arguments are invalid, FSL_RC_TYPE if any
   of the content appears to be binary (contains embedded NUL
   bytes), FSL_RC_RANGE if some range is exceeded (e.g. the maximum
   number of input lines).

   None of (pA, pB, out) may be NULL.

   TODOs:

   - Add a predicate function for outputing only matching
   differences, analog to fossil(1)'s regex support (but more
   flexible).

   - Expose the raw diff-generation bits via the internal API
   to facilitate/enable the creation of custom diff formats.
*/
FSL_EXPORT int fsl_diff_text(fsl_buffer const *pA, fsl_buffer const *pB,
                             fsl_output_f out, void * outState,
                             short contextLines, short sbsWidth,
                             int diffFlags );

/**
   Functionally equivalent to:

   @code:
   fsl_diff_text(pA, pB, fsl_output_f_buffer, pOut,
   contextLines, sbsWidth, diffFlags);
   @endcode

   Except that it returns FSL_RC_MISUSE if !pOut.
*/
FSL_EXPORT int fsl_diff_text_to_buffer(fsl_buffer const *pA, fsl_buffer const *pB,
                                       fsl_buffer *pOut, short contextLines,
                                       short sbsWidth, int diffFlags );

/**
   If zDate is an ISO8601-format string, optionally with a .NNN
   fractional suffix, then this function returns true and sets
   *pOut (if pOut is not NULL) to the corresponding Julian
   value. If zDate is not an ISO8601-format string then this
   returns false (0) and pOut is not modified.

   This function does NOT confirm that zDate ends with a NUL
   byte. i.e.  if passed a valid date string which has trailing
   bytes after it then those are simply ignored. This is so that it
   can be used to read subsets of larger strings.

   Achtung: this calculation may, due to voodoo-level
   floating-point behaviours, differ by a small fraction of a point
   (at the millisecond level) for a given input compared to other
   implementations (e.g. sqlite's strftime() _might_ differ by a
   millisecond or two or _might_ not). Thus this routine should not
   be used when 100% round-trip fidelity is required, but is close
   enough for routines which do not require 100% millisecond-level
   fidelity in time conversions.

   @see fsl_julian_to_iso8601()
*/
FSL_EXPORT char fsl_iso8601_to_julian( char const * zDate, double * pOut );

/** 
    Converts the Julian Day J to an ISO8601 time string. If addMs is
    true then the string includes the '.NNN' fractional part, else
    it will not. This function writes (on success) either 20 or 24
    bytes (including the terminating NUL byte) to pOut, depending on
    the value of addMs, and it is up to the caller to ensure that
    pOut is at least that long.

    Returns true (non-0) on success and the only error conditions
    [it can catch] are if pOut is NULL, J is less than 0, or
    evaluates to a time value which does not fit in ISO8601
    (e.g. only years 0-9999 are supported).

    @see fsl_iso8601_to_julian()
*/
FSL_EXPORT char fsl_julian_to_iso8601( double J, char * pOut, bool addMs );

/**
   Returns the Julian Day time J value converted to a Unix Epoch
   timestamp. It assumes 86400 seconds per day and does not account
   for any sort leap seconds, leap years, leap frogs, or any other
   kind of leap, up to and including a leap of faith.
*/
FSL_EXPORT fsl_time_t fsl_julian_to_unix( double J );

/**
   Performs a chdir() to the directory named by zChDir.

   Returns 0 on success. On error it tries to convert the
   underlying errno to one of the FSL_RC_xxx values, falling
   back to FSL_RC_IO if it cannot figure out anything more
   specific.
*/
FSL_EXPORT int fsl_chdir(const char *zChDir);

/**
   A strftime() implementation.

   dest must be valid memory at least destLen bytes long. The result
   will be written there.

   fmt must contain the format string. See the file fsl_strftime.c
   for the complete list of format specifiers and their descriptions.

   timeptr must be the time the caller wants to format.

   Returns 0 if any arguments are NULL.

   On success it returns the number of bytes written to dest, not
   counting the terminating NUL byte (which it also writes). It
   returns 0 on any error, and the client may need to distinguish
   between real errors and (destLen==0 or !*fmt), both of which could
   also look like errors.

   TODOs:

   - Refactor this to take a callback or a fsl_buffer, so that we can
   format arbitrarily long output.

   - Refactor it to return an integer error code.

   (This implementation is derived from public domain sources
   dating back to the early 1990's.)
*/
FSL_EXPORT fsl_size_t fsl_strftime(char *dest, fsl_size_t destLen,
                        const char *format, const struct tm *timeptr);

/**
   A convenience form of fsl_strftime() which takes its timestamp in
   the form of a Unix Epoch time. See fsl_strftime() for the
   semantics of the first 3 arguments and the return value. If
   convertToLocal is true then epochTime gets converted to local
   time (via, oddly enough, localtime(3)), otherwise gmtime(3) is
   used for the conversion.

   BUG: this function uses static state and is not thread-safe.
*/
FSL_EXPORT fsl_size_t fsl_strftime_unix(char * dest, fsl_size_t destLen, char const * format,
                             fsl_time_t epochTime, bool convertToLocal);


/**
   A convenience form of fsl_strftime() which assumes that the
   formatted string is of "some reasonable size" and appends its
   formatted representation to b. Returns 0 on success, non-0 on
   error. If any argument is NULL or !*format then FSL_RC_MISUSE is
   returned. FSL_RC_RANGE is returned if the underlying call to
   fsl_strftime() fails (which it will if the format string
   resolves to something "unususually long"). It returns FSL_RC_OOM
   if appending to b fails due to an allocation error.
*/
FSL_EXPORT int fsl_buffer_strftime(fsl_buffer * b, char const * format, const struct tm *timeptr);

/**
   "whence" values for use with fsl_buffer_seek.
*/
enum fsl_buffer_seek_e {
FSL_BUFFER_SEEK_SET = 1,
FSL_BUFFER_SEEK_CUR = 2,
FSL_BUFFER_SEEK_END = 3
};
typedef enum fsl_buffer_seek_e fsl_buffer_seek_e;

/**
   "Seeks" b's internal cursor to a position specified by the given offset
   from either the current cursor position (FSL_BUFFER_SEEK_CUR), the start
   of the buffer (FSL_BUFFER_SEEK_SET), or the end (FSL_BUFFER_SEEK_END).
   If the cursor would be placed out of bounds, it will be placed at the start
   resp. end of the buffer.

   The "end" of a buffer is the value of its fsl_buffer::used member
   (i.e.  its one-after-the-end).

   Returns the new position.

   Note that most buffer algorithms, e.g. fsl_buffer_append(), do not
   modify the cursor. Only certain special-case algorithms use it.

   @see fsl_buffer_tell()
   @see fsl_buffer_rewind()
*/
FSL_EXPORT fsl_size_t fsl_buffer_seek(fsl_buffer * b, fsl_int_t offset,
                                      fsl_buffer_seek_e  whence);
/**
   Returns the buffer's current cursor position.

   @see fsl_buffer_rewind()
   @see fsl_buffer_seek()
*/
FSL_EXPORT fsl_size_t fsl_buffer_tell(fsl_buffer const *b);
/**
   Resets b's cursor to the beginning of the buffer.

   @see fsl_buffer_tell()
   @see fsl_buffer_seek()
*/
FSL_EXPORT void fsl_buffer_rewind(fsl_buffer *b);

/**
   The "Path Finder" class is a utility class for searching the
   filesystem for files matching a set of common prefixes and/or
   suffixes (i.e. directories and file extensions).

   Example usage:

   @code
   fsl_pathfinder pf = fsl_pathfinder_empty;
   int rc;
   char const * found = NULL;
   rc = fsl_pathfinder_ext_add( &pf, ".doc" );
   if(rc) { ...error... }
   // The following error checks are elided for readability:
   rc = fsl_pathfinder_ext_add( &pf, ".txt" );
   rc = fsl_pathfinder_ext_add( &pf, ".wri" );
   rc = fsl_pathfinder_dir_add( &pf, "." );
   rc = fsl_pathfinder_dir_add( &pf, "/my/doc/dir" );
   rc = fsl_pathfinder_dir_add( &pf, "/other/doc/dir" );

   rc = fsl_pathfinder_search( &pf, "MyDoc", &found, NULL);
   if(0==rc){ assert(NULL!=found); }

   // Eventually clean up:
   fsl_pathfinder_clear(&pf);
   @endcode

   @see fsl_pathfinder_dir_add()
   @see fsl_pathfinder_ext_add()
   @see fsl_pathfinder_clear()
   @see fsl_pathfinder_search()
*/
struct fsl_pathfinder {
  /**
     Holds the list of search extensions. Each entry
     is a (char *) owned by this object.
  */
  fsl_list ext;
  /**
     Holds the list of search directories. Each entry is a (char *)
     owned by this object.
  */
  fsl_list dirs;
  /**
     Used to build up a path string during fsl_pathfinder_search(),
     and holds the result of a successful search. We use a buffer,
     as opposed to a simple string, because (A) it simplifies the
     search implementation and (B) reduces allocations (it gets
     reused for each search).
  */
  fsl_buffer buf;
};

typedef struct fsl_pathfinder fsl_pathfinder;
/**
   Initialized-with-defaults fsl_pathfinder instance, intended for
   const copy initialization.
*/
#define fsl_pathfinder_empty_m {fsl_list_empty_m/*ext*/,fsl_list_empty_m/*dirs*/,fsl_buffer_empty_m/*buf*/}

/**
   Initialized-with-defaults fsl_pathfinder instance, intended for
   copy initialization.
*/
FSL_EXPORT const fsl_pathfinder fsl_pathfinder_empty;

/**
   Frees all memory associated with pf, but does not free pf.
   Is a no-op if pf is NULL.
*/
FSL_EXPORT void fsl_pathfinder_clear(fsl_pathfinder * pf);

/**
   Adds the given directory to pf's search path. Returns 0 on
   success, FSL_RC_MISUSE if !pf or !dir (dir _may_ be an empty
   string), FSL_RC_OOM if copying the string or adding it to the
   list fails.

   @see fsl_pathfinder_ext_add()
   @see fsl_pathfinder_search() 
*/
FSL_EXPORT int fsl_pathfinder_dir_add(fsl_pathfinder * pf, char const * dir);

/**
   Adds the given directory to pf's search extensions. Returns 0 on
   success, FSL_RC_MISUSE if !pf or !dir (dir _may_ be an empty
   string), FSL_RC_OOM if copying the string or adding it to the
   list fails.

   Note that the client is responsible for adding a "." to the
   extension, if needed, as this API does not apply any special
   meaning to any characters in a search extension. e.g. "-journal"
   and "~" are both perfectly valid extensions for this purpose.

   @see fsl_pathfinder_dir_add()
   @see fsl_pathfinder_search() 
*/
FSL_EXPORT int fsl_pathfinder_ext_add(fsl_pathfinder * pf, char const * ext);

/**
   Searches for a file whose name can be constructed by some
   combination of pf's directory/suffix list and the given base
   name.

   It searches for files in the following manner:

   If the 2nd parameter exists as-is in the filesystem, it is
   treated as a match, otherwise... Loop over all directories
   in pf->dirs. Create a path with DIR/base, or just base if
   the dir entry is empty (length of 0). Check for a match.
   If none is found, then... Loop over each extension in
   pf->ext, creating a path named DIR/baseEXT (note that it
   does not add any sort of separator between the base and the
   extensions, so "~" and "-foo" are legal extensions). Check
   for a match.

   On success (a readable filesystem entry is found):

   - It returns 0.

   - If pOut is not NULL then *pOut is set to the path it
   found. The bytes of the returned string are only valid until the
   next search operation on pf, so copy them if you need them.
   Note that the returned path is _not_ normalized via
   fsl_file_canonical_name() or similar, and it may very well
   return a relative path (if base or one of pf->dirs contains a
   relative path part).

   - If outLen is not NULL, *outLen will be set to the
   length of the returned string. 

   On error:

   - Returns FSL_RC_MISUSE if !pf, !base, !*base.

   - Returns FSL_RC_OOM on allocation error (it uses a buffer to
   hold its path combinations and return value).

   - Returns FSL_RC_NOT_FOUND if it finds no entry.

   The host platform's customary path separator is used to separate
   directory/file parts ('\\' on Windows and '/' everywhere else).

   Note that it _is_ legal for pOut and outLen to both be NULL, in
   which case a return of 0 signals that an entry was found, but
   the client has no way of knowing what path it might be (unless,
   of course, he relies on internal details of the fsl_pathfinder
   API, which he most certainly should not do).

   Tip: if the client wants to be certain that this function will
   not allocate memory, simply use fsl_buffer_reserve() on pf->buf
   to reserve the desired amount of space in advance. As long as
   the search paths never extend that length, this function will
   not need to allocate. (Until/unless the following TODO is
   implemented...)

   Potential TODO: use fsl_file_canonical_name() so that the search
   dirs themselves do not need to be entered using
   platform-specific separators. The main reason it's not done now
   is that it requires another allocation. The secondary reason is
   because it's sometimes useful to use relative paths in this
   context (based on usage in previous trees from which this code
   derives).

   @see fsl_pathfinder_dir_add()
   @see fsl_pathfinder_ext_add()
   @see fsl_pathfinder_clear()
*/
FSL_EXPORT int fsl_pathfinder_search(fsl_pathfinder * pf, char const * base,
                                     char const ** pOut, fsl_size_t * outLen );


/**
   A utility class for creating ZIP-format archives. All members
   are internal details and must not be mucked about with by the
   client.  See fsl_zip_file_add() for an example of how to use it.

   Note that it creates ZIP content in memory, as opposed to
   streaming it (it is not yet certain if abstractly streaming a
   ZIP is possible), so creating a ZIP file this way is exceedingly
   memory-hungry.

   @see fsl_zip_file_add()
   @see fsl_zip_timestamp_set_julian()
   @see fsl_zip_timestamp_set_unix()
   @see fsl_zip_end()
   @see fsl_zip_body()
   @see fsl_zip_finalize()
*/
struct fsl_zip_writer {
  /**
     Number of entries (files + dirs) added to the zip file so far.
  */
  fsl_size_t entryCount;
  /**
     Current DOS-format time of the ZIP.
  */
  int32_t dosTime;
  /**
     Current DOS-format date of the ZIP.
  */
  int32_t dosDate;
  /**
     Current Unix Epoch time of the ZIP.
  */
  fsl_time_t unixTime;
  /**
     An artificial root directory which gets prefixed
     to all inserted filenames.
  */
  char * rootDir;
  /**
     The buffer for the table of contents.
  */
  fsl_buffer toc;
  /**
     The buffer for the ZIP file body.
  */
  fsl_buffer body;
  /**
     Internal scratchpad for ops which often allocate
     small buffers.
  */
  fsl_buffer scratch;
  /**
     The current list of directory entries (as (char *)).
  */
  fsl_list dirs;
};
typedef struct fsl_zip_writer fsl_zip_writer;

/**
   An initialized-with-defaults fsl_zip_writer instance, intended
   for in-struct or const-copy initialization.
*/
#define fsl_zip_writer_empty_m {                \
  0/*entryCount*/,                            \
  0/*dosTime*/,                             \
  0/*dosDate*/,                             \
  0/*unixTime*/,                            \
  NULL/*rootDir*/,                          \
  fsl_buffer_empty_m/*toc*/,                \
  fsl_buffer_empty_m/*body*/,               \
  fsl_buffer_empty_m/*scratch*/,            \
  fsl_list_empty_m/*dirs*/                  \
}

/**
   An initialized-with-defaults fsl_zip_writer instance,
   intended for copy-initialization.
*/
FSL_EXPORT const fsl_zip_writer fsl_zip_writer_empty;

/**
   Sets a virtual root directory in z, such that all files added
   with fsl_zip_file_add() will get this directory prefixed to
   it.

   If zRoot is NULL or empty then this clears the virtual root,
   otherwise is injects any directory levels it needs to into the
   being-generated ZIP. Note that zRoot may contain multiple levels
   of directories, e.g. "foo/bar/baz", but it must be legal for
   use in a ZIP file.

   This routine copies zRoot's bytes, so they may be transient.

   Returns 0 on success, FSL_RC_ERROR if !z, FSL_RC_OOM on
   allocation error. Returns FSL_RC_RANGE if zRoot is an absolute
   path or if zRoot cannot be normalized to a "simplified name" (as
   per fsl_is_simple_pathname(), with the note that this routine
   will pass a copy of zRoot through fsl_file_simplify_name()
   first).

   @see fsl_zip_finalize()
*/
FSL_EXPORT int fsl_zip_root_set(fsl_zip_writer * z, char const * zRoot );

/**
   Adds a file or directory to the ZIP writer z. zFilename is the
   virtual name of the file or directory. If pContent is NULL then
   it is assumed that we are creating one or more directories,
   otherwise the ZIP's entry is populated from pContent. The
   permsFlag argument specifies the fossil-specific permission
   flags from the fsl_fileperm_e enum, but currently ignores the
   permsFlag argument for directories. Not that this function
   creates directory entries for any files automatically, so there
   is rarely a need for client code to create them (unless they
   specifically want to ZIP an empty directory entry).

   Notes of potential interest:

   - The ZIP is created in memory, and thus creating ZIPs with this
   API is exceedingly memory-hungry.

   - The timestamp of any given file must be set separately from
   this call using fsl_zip_timestamp_set_unix() or
   fsl_zip_timestamp_set_julian(). That value is then used for
   subsequent file-adds until a new time is set.

   - If a root directory has been set using fsl_zip_root_set() then
   that name, plus '/' (if the root does not end with one) gets
   prepended to all files added via this routine.

   An example of the ZIP-generation process:

   @code
   int rc;
   fsl_zip_writer z = fsl_zip_writer_empty;
   fsl_buffer buf = fsl_buffer_empty;
   fsl_buffer const * zipBody;

   // ...fill the buf buffer (not shown here)...

   // Optionally set a virtual root dir for new files:
   rc = fsl_zip_root_set( &z, "myRootDir" ); // trailing slash is optional
   if(rc) { ... error ...; goto end; }

   // We must set a timestamp which will be used until we set another:
   fsl_zip_timestamp_set_unix( &z, time(NULL) );

   // Add a file:
   rc = fsl_zip_file_add( &z, "foo/bar.txt", &buf, FSL_FILE_PERM_REGULAR );
   // Clean up our content:
   fsl_buffer_reuse(&buf); // only needed if we want to re-use the buffer's memory
   if(rc) goto end;

   // ... add more files the same way (not shown) ...

   // Now "seal" the ZIP file:
   rc = fsl_zip_end( &z );
   if(rc) goto end;

   // Fetch the ZIP content:
   zipBody = fsl_zip_body( &z );
   // zipBody now points to zipBody->used bytes of ZIP file content
   // which can be sent to an arbitrary destination, e.g.:
   rc = fsl_buffer_to_filename( zipBody, "my.zip" );

   end:
   fsl_buffer_clear(&buf);
   // VERY important, once we're done with z:
   fsl_zip_finalize( &z );
   if(rc){...we had an error...}
   @endcode

   @see fsl_zip_timestamp_set_julian()
   @see fsl_zip_timestamp_set_unix()
   @see fsl_zip_end()
   @see fsl_zip_body()
   @see fsl_zip_finalize()
*/
FSL_EXPORT int fsl_zip_file_add( fsl_zip_writer * z, char const * zFilename,
                                 fsl_buffer const * pContent, int permsFlag );

/**
   Ends the ZIP-creation process, padding all buffers, writing all
   final required values, and freeing up most of the memory owned
   by z. After calling this, z->body contains the full generated
   ZIP file.

   Returns 0 on success. On error z's contents may still be
   partially intact (for debugging purposes) and z->body will not
   hold complete/valid ZIP file contents. Results are undefined if
   !z or z has not been properly initialized.

   The caller must eventually pass z to fsl_zip_finalize() to free
   up any remaining resources.

   @see fsl_zip_timestamp_set_julian()
   @see fsl_zip_timestamp_set_unix()
   @see fsl_zip_file_add()
   @see fsl_zip_body()
   @see fsl_zip_finalize()
   @see fsl_zip_end_take()
*/
FSL_EXPORT int fsl_zip_end( fsl_zip_writer * z );

/**
   This variant of fsl_zip_end() transfers the current contents
   of the zip's body to dest, replacing (freeing) any contents it may
   hold when this is called, then passes z to fsl_zip_finalize()
   to free any other resources (which are invalidated by the removal
   of the body).

   Returns 0 on success, FSL_RC_MISUSE if either pointer is NULL,
   some non-0 code if the proxied fsl_zip_end() call fails. On
   error, the transfer of contents to dest does NOT take place, but
   z is finalized (if it is not NULL) regardless of success or
   failure (even if dest is NULL). i.e. on error z is still cleaned
   up.
*/
FSL_EXPORT int fsl_zip_end_take( fsl_zip_writer * z, fsl_buffer * dest );

/**
   This variant of fsl_zip_end_take() passes z to fsl_zip_end(),
   write's the ZIP body to the given filename, passes
   z to fsl_zip_finalize(), and returns the result of
   either end/save combination. Saving is not attempted
   if ending the ZIP fails.

   On success 0 is returned and the contents of the ZIP are in the
   given file. On error z is STILL cleaned up, and the file might
   have been partially populated (only on I/O error after writing
   started). In either case, z is cleaned up and ready for re-use or
   (in the case of a heap-allocated instance) freed.
*/
FSL_EXPORT int fsl_zip_end_to_filename( fsl_zip_writer * z, char const * filename );


/**
   Returns a pointer to z's ZIP content buffer. The contents
   are ONLY valid after fsl_zip_end() returns 0.

   @see fsl_zip_timestamp_set_julian()
   @see fsl_zip_timestamp_set_unix()
   @see fsl_zip_file_add()
   @see fsl_zip_end()
   @see fsl_zip_end_take()
   @see fsl_zip_finalize()
*/
FSL_EXPORT fsl_buffer const * fsl_zip_body( fsl_zip_writer const * z );

/**
   Frees all memory owned by z and resets it to a clean state, but
   does not free z. Any fsl_zip_writer instance which has been
   modified via the fsl_zip_xxx() family of functions MUST
   eventually be passed to this function to clean up any contents
   it might have accumulated during its life. After this returns,
   z is legal for re-use in creating a new ZIP archive.

   @see fsl_zip_timestamp_set_julian()
   @see fsl_zip_timestamp_set_unix()
   @see fsl_zip_file_add()
   @see fsl_zip_end()
   @see fsl_zip_body()
*/
FSL_EXPORT void fsl_zip_finalize(fsl_zip_writer * z);

/**
   Set z's date and time from a Julian Day number. Results are
   undefined if !z. Results will be invalid if rDate is negative. The
   timestamp is applied to all fsl_zip_file_add() operations until it
   is re-set.

   @see fsl_zip_timestamp_set_unix()
   @see fsl_zip_file_add()
   @see fsl_zip_end()
   @see fsl_zip_body()
*/
FSL_EXPORT void fsl_zip_timestamp_set_julian(fsl_zip_writer *z, double rDate);

/**
   Set z's date and time from a Unix Epoch time. Results are
   undefined if !z. Results will be invalid if rDate is negative. The
   timestamp is applied to all fsl_zip_file_add() operations until it
   is re-set.
*/
FSL_EXPORT void fsl_zip_timestamp_set_unix(fsl_zip_writer *z, fsl_time_t epochTime);

/**
   State for the fsl_timer_xxx() family of functions.

   @see fsl_timer_start()
   @see fsl_timer_reset()
   @see fsl_timer_stop()
*/
struct fsl_timer_state {
  /**
     The amount of time (microseconds) spent in "user space."
  */
  uint64_t user;
  /**
     The amount of time (microseconds)spent in "kernel space."
  */
  uint64_t system;
};
typedef struct fsl_timer_state fsl_timer_state;

/**
   Initialized-with-defaults fsl_timer_state_empty instance,
   intended for const copy initialization.
*/
#define fsl_timer_state_empty_m {0,0}

/**
   Initialized-with-defaults fsl_timer_state_empty instance,
   intended for copy initialization.
*/
FSL_EXPORT const fsl_timer_state fsl_timer_state_empty;

/**
   Sets t's counter state to the current CPU timer usage, as
   determined by the OS.

   Achtung: timer support is only enabled if the library is built
   with the FSL_CONFIG_ENABLE_TIMER macro set to a true value (it is
   on by default).

   @see fsl_timer_reset()
   @see fsl_timer_stop()
*/
FSL_EXPORT void fsl_timer_start(fsl_timer_state * t);

/**
   Returns the difference in _CPU_ times in microseconds since t was
   last passed to fsl_timer_start() or fsl_timer_reset().  It might
   return 0 due to system-level precision restrictions. Note that this
   is not useful for measuring wall times.
*/
FSL_EXPORT uint64_t fsl_timer_fetch(fsl_timer_state const * t);

/**
   Resets t to the current time and returns the number of microseconds
   since t was started or last reset.

   @see fsl_timer_start()
   @see fsl_timer_reset()
*/
FSL_EXPORT uint64_t fsl_timer_reset(fsl_timer_state * t);

/**
   Clears t's state and returns the difference (in uSec) between the
   last time t was started or reset, as per fsl_timer_fetch().

   @see fsl_timer_start()
   @see fsl_timer_reset()
*/
FSL_EXPORT uint64_t fsl_timer_stop(fsl_timer_state *t);

/**
   For the given red/green/blue values (all in the range of 0 to
   255, or truncated to be so!) this function returns the RGB
   encoded in the lower 24 bits of a single number. See
   fsl_gradient_color() for an explanation and example.

   For those asking themselves, "why does an SCM API have a function
   for encoding RGB colors?" the answer is: fossil(1) has a long
   history of using HTML color codes to set the color of branches,
   and this is provided in support of such features.

   @see fsl_rgb_decode()
   @see fsl_gradient_color()
*/
FSL_EXPORT unsigned int fsl_rgb_encode( int r, int g, int b );

/**
   Given an RGB-encoded source value, this function decodes
   the lower 24 bits into r, g, and b. Any of r, g, and b may
   be NULL to skip over decoding of that part.

   @see fsl_rgb_encode()
   @see fsl_gradient_color()
*/
FSL_EXPORT void fsl_rgb_decode( unsigned int src, int *r, int *g, int *b );

/**
   For two color values encoded as RRGGBB values (see below for the
   structure), this function computes a gradient somewhere between
   those colors. c1 and c2 are the edges of the gradient.
   numberOfSteps is the number of steps in the gradient. stepNumber
   is a number less than numberOfSteps which specifies the "degree"
   of the gradients. If either numberOfSteps or stepNumber is 0, c1
   is returned. stepNumber of equal to or greater than c2 returns
   c2.

   The returns value is an RGB-encoded value in the lower 24 bits,
   ordered in big-endian. In other words, assuming rc is the return
   value:
     
   - red   = (rc&0xFF0000)>>16
   - green = (rc&0xFF00)>>8
   - blue  = (rc&0xFF)

   Or use fsl_rgb_decode() to break it into its component parts.

   It can be passed directly to a printf-like function, using the
   hex-integer format specifier, e.g.:

   @code
   fsl_buffer_appendf(&myBuf, "#%06x", rc);
   @endcode

   Tip: for a given HTML RRGGBB value, its C representation is
   identical: HTML \#F0D0A0 is 0xF0D0A0 in C.

   @see fsl_rgb_encode()
   @see fsl_rgb_decode()
*/
FSL_EXPORT unsigned int fsl_gradient_color(unsigned int c1, unsigned int c2,
                                           unsigned int numberOfSteps,
                                           unsigned int stepNumber);

/**
   "Simplifies" an SQL string by making the following modifications
   inline:

   - Consecutive non-newline spaces outside of an SQL string are
   collapsed into one space.

   - Consecutive newlines outside of an SQL string are collapsed into
   one space.

   Contents of SQL strings are not transformed in any way.

   len must be the length of the sql string. If it is negative,
   fsl_strlen(sql) is used to calculate the length.

   Returns the number of bytes in the modified string (its strlen) and
   NUL-terminates it at the new length. Thus the input string must be
   at least one byte longer than its virtual length (its NUL
   terminator byte suffices, provided it is NUL-terminated, as we can
   safely overwrite that byte).

   If !sql or its length resolves to 0, this function returns 0
   without side effects.
*/
FSL_EXPORT fsl_size_t fsl_simplify_sql( char * sql, fsl_int_t len );

/**
   Convenience form of fsl_simplify_sql() which assumes b holds an SQL
   string. It gets processed by fsl_simplify_sql() and its 'used'
   length potentially gets adjusted to match the adjusted SQL string.
*/
FSL_EXPORT fsl_size_t fsl_simplify_sql_buffer( fsl_buffer * b );


/**
   Returns the result of calling the platform's equivalent of
   isatty(fd). e.g. on Windows this is _isatty() and on Unix
   isatty(). i.e. it returns a true value (non-0) if it thinks that
   the given file descriptor value is attached to an interactive
   terminal, else it returns false.
*/
FSL_EXPORT char fsl_isatty(int fd);


/**

   A container type for lists of db record IDs. This is used in
   several places as a cache for record IDs, to keep track of ones
   we know about, ones we know that we don't know about, and to
   avoid duplicate processing in some contexts.
*/
struct fsl_id_bag {
  /**
     Number of entries of this->list which are in use (have a
     positive value). They need not be contiguous!  Must be <=
     capacity.
  */
  fsl_size_t entryCount;
  /**
     The number of elements allocated for this->list.
  */
  fsl_size_t capacity;
  /**
     The number of elements in this->list which have a zero or
     positive value. Must be <= capacity.
  */
  fsl_size_t used;
  /**
     Array of IDs this->capacity elements long. "Used" elements
     have a positive value. Unused ones are set to 0.
  */
  fsl_id_t * list;
};

/**
   Initialized-with-defaults fsl_id_bag structure,
   intended for copy initialization.
*/
FSL_EXPORT const fsl_id_bag fsl_id_bag_empty;

/**
   Initialized-with-defaults fsl_id_bag structure,
   intended for in-struct initialization.
*/
#define fsl_id_bag_empty_m {                    \
    0/*entryCount*/, 0/*capacity*/,             \
    0/*used*/, NULL/*list*/ }

/**

   Return the number of elements in the bag.
*/
FSL_EXPORT fsl_size_t fsl_id_bag_count(fsl_id_bag const *p);

/**

   Remove element e from the bag if it exists in the bag. If e is not
   in the bag, this is a no-op. Returns true if it removes an element,
   else false.

   e must be positive. Results are undefined if e<=0.
*/
FSL_EXPORT bool fsl_id_bag_remove(fsl_id_bag *p, fsl_id_t e);

/**
   Returns true if e is in the given bag. Returns false if it is
   not. It is illegal to pass an e value of 0, and that will trigger
   an assertion in debug builds. In non-debug builds, behaviour if
   passed 0 is undefined.
*/
FSL_EXPORT bool fsl_id_bag_contains(fsl_id_bag const *p, fsl_id_t e);

/**
   Insert element e into the bag if it is not there already.  Returns
   0 if it actually inserts something or if it already contains such
   an entry, and some other value on error (namely FSL_RC_OOM on
   allocation error).

   e must be positive or an assertion is triggered in debug builds. In
   non-debug builds, behaviour if passed 0 is undefined.
*/
FSL_EXPORT int fsl_id_bag_insert(fsl_id_bag *p, fsl_id_t e);

/**
   Returns the ID of the first element in the bag.  Returns 0 if the
   bag is empty.

   Example usage:

   @code
   fsl_id_t nid;
   for( nid = fsl_id_bag_first(&list);
   nid > 0;
   nid = fsl_id_bag_next(&list, nid)){
     
   ...do something...
   }
   @endcode
*/
FSL_EXPORT fsl_id_t fsl_id_bag_first(fsl_id_bag const *p);

/**
   Returns the next element in the bag after e.  Return 0 if e is
   the last element in the bag.  Any insert or removal from the bag
   might reorder the bag. It is illegal to pass this 0 (and will
   trigger an assertion in debug builds). For the first call, pass
   it the non-0 return value from fsl_id_bag_first(). For
   subsequent calls, pass the previous return value from this
   function.

   @see fsl_id_bag_first()
*/
FSL_EXPORT fsl_id_t fsl_id_bag_next(fsl_id_bag const *p, fsl_id_t e);

/**
   Swaps the contents of the given bags.
*/
FSL_EXPORT void fsl_id_bag_swap(fsl_id_bag *lhs, fsl_id_bag *rhs);

/**
   Frees any memory owned by p, but does not free p.
*/
FSL_EXPORT void fsl_id_bag_clear(fsl_id_bag *p);

/**
   Resets p's internal list, effectively emptying it for re-use, but
   does not free its memory. Immediately after calling this
   fsl_id_bag_count() will return 0.
*/
FSL_EXPORT void fsl_id_bag_reset(fsl_id_bag *p);


/**
   Returns true if p contains a fossil-format merge conflict marker,
   else returns false.
   
   @see fsl_buffer_merge3()
*/
FSL_EXPORT bool fsl_buffer_contains_merge_marker(fsl_buffer const *p);

/**
   Performs a three-way merge.
   
   The merge is an edit against pV2. Both pV1 and pV2 have a common
   origin at pPivot. Apply the changes of pPivot ==> pV1 to pV2,
   appending them to pOut. (Pedantic side-note: the input buffers are
   not const because we need to manipulate their cursors, but their
   buffered memory is not modified.)

   If merge conflicts are encountered, it continues as best as it can
   and injects "indiscrete" markers in the output to denote the nature
   of each conflict. If conflictCount is not NULL then on success the
   number of merge conflicts is written to *conflictCount.

   Returns 0 on success, FSL_RC_OOM on OOM, FSL_RC_TYPE if any input
   appears to be binary. 

   @see fsl_buffer_contains_merge_marker()
*/
FSL_EXPORT int fsl_buffer_merge3(fsl_buffer *pPivot, fsl_buffer *pV1, fsl_buffer *pV2,
                                 fsl_buffer *pOut, unsigned int *conflictCount);

/**
   Event IDs for use with fsl_confirm_callback_f implementations.

   The idea here is to send, via callback, events from the library to
   the client when a potentially interactive response is necessary.
   We define a bare minimum of information needed for the client to
   prompt a user for a response. To that end, the interface passes on
   2 pieces of information to the client: the event ID and a filename.
   It is up to the application to translate that ID into a
   user-readable form, get a response (using a well-defined set of
   response IDs), and convey that back to the
   library via the callback's result pointer interface.

   This enum will be extended as the library develops new requirements
   for interactive use.

   @see fsl_confirm_response_e
*/
enum fsl_confirm_event_e {
/**
   Sentinal value.
*/
FSL_CEVENT_INVALID = 0,
/**
   An operation requests permission to overwrite a locally-modified
   file. e.g. when performing a checkout over a locally-edited
   version. Overwrites of files which are known to be in the previous
   (being-overwritten) checkout version are automatically overwritten.
*/
FSL_CEVENT_OVERWRITE_MOD_FILE = 1,
/**
   An operation requests permission to overwrite an SCM-unmanaged file
   with one which is managed by SCM. This can happen, e.g., when
   switching from a version which did not contain file X, but had file
   X on disk, to a version which contains file X.
*/
FSL_CEVENT_OVERWRITE_UNMGD_FILE = 2,
/**
   An operation requests permission to remove a LOCALLY-MODIFIED file
   which has been removed from SCM management. e.g. when performing a
   checkout over a locally-edited version and an edited file was
   removed from the SCM somewhere between those two versions.
   UMODIFIED files which are removed from the SCM between two
   checkouts are automatically removed on the grounds that it poses no
   data loss risk because the other version is "somewhere" in the SCM.
*/
FSL_CEVENT_RM_MOD_UNMGD_FILE = 3,

/**
   Indicates that the library cannot determine which of multiple
   potential versions to choose from and requires the user to
   select one.
*/
FSL_CEVENT_MULTIPLE_VERSIONS = 4

};
typedef enum fsl_confirm_event_e fsl_confirm_event_e;

/**
   Answers to questions posed to clients via the
   fsl_confirm_callback_f() interface.

   This enum will be extended as the library develops new requirements
   for interactive use.

   @see fsl_confirm_event_e
*/
enum fsl_confirm_response_e {
/**
   Sentinel/default value - not a valid answer. Guaranteed to have a
   value of 0. No other entries in this enum are guaranteed to have
   well-known/stable values: always use the enum symbols instead of
   integer values.
*/
FSL_CRESPONSE_INVALID = 0,
/**
   Accept the current event and continue processes.
*/
FSL_CRESPONSE_YES = 1,
/**
   Reject the current event and continue processes.
*/
FSL_CRESPONSE_NO = 2,
/**
   Reject the current event and stop processesing. Cancellation is
   generally considered to be a recoverable error.
*/
FSL_CRESPONSE_CANCEL = 3,
/**
   Accept the current event and all identical event types for the
   current invocation of this particular SCM operation.
*/
FSL_CRESPONSE_ALWAYS = 5,
/**
   Reject the current event and all identical event types for the
   current invocation of this particular SCM operation.
*/
FSL_CRESPONSE_NEVER = 6,
/**
   For events which are documented as being multiple-choice,
   this answer indicates that the client has set the index of
   their choice in the fsl_confirm_response::multipleChoice
   field:

   - FSL_CEVENT_MULTIPLE_VERSIONS
*/
FSL_CRESPONSE_MULTI = 7
};
typedef enum fsl_confirm_response_e fsl_confirm_response_e;

/**
   A response for use with the fsl_confirmer API. It is intended to
   encapsulate, with a great deal of abstraction, answers to typical
   questions which the library may need to interactively query a user
   for. e.g. confirmation about whether to overwrite a file or which
   one of 3 versions to select.

   This type will be extended as the library develops new requirements
   for interactive use.
*/
struct fsl_confirm_response {
  /**
     Client response to the current fsl_confirmer question.
  */
  fsl_confirm_response_e response;
  /**
     If this->response is FSL_CRESPONSE_MULTI then this must be set to
     the index of the client's multiple-choice answer.

     Events which except this in their response:

     - FSL_CEVENT_MULTIPLE_VERSIONS
  */
  uint16_t multipleChoice;
};
/**
   Convenience typedef.
*/
typedef struct fsl_confirm_response fsl_confirm_response;

/**
   Empty-initialized fsl_confirm_detail instance to be used for
   const copy initialization.
*/
#define fsl_confirm_response_empty_m {FSL_CRESPONSE_INVALID, -1}

/**
   Empty-initialized fsl_confirm_detail instance to be used for
   non-const copy initialization.
*/
FSL_EXPORT const fsl_confirm_response fsl_confirm_response_empty;

/**
   A struct for passing on interactive questions to
   fsl_confirmer_callback_f implementations.
*/
struct fsl_confirm_detail {
  /**
     The message ID of this confirmation request. This value
     determines how the rest of this struct's values are to
     be interpreted.
  */
  fsl_confirm_event_e eventId;
  /**
     Depending on the eventId, this might be NULL or might refer to a
     filename. This will be a filename for following confirmations
     events:

     - FSL_CEVENT_OVERWRITE_MOD_FILE
     - FSL_CEVENT_OVERWRITE_UNMGD_FILE
     - FSL_CEVENT_RM_MOD_UNMGD_FILE

     For all others it will be NULL.

     Whether this name refers to an absolute or relative path is
     context-dependent, and not specified by this API. In general,
     relative paths should be used if/when what they are relative to
     (e.g. a checkout root) is/should be clear to the user. The intent
     is that applications can display that name to the user in a UI
     control, so absolute paths "should" "generally" be avoided
     because they can be arbitrarily long.
  */
  const char * filename;
  /**
     Depending on the eventId, this might be NULL or might
     refer to a list of details of a type specified in the
     documentation for that eventId.

     Implementation of such an event is still TODO, but we have at
     least one use case lined up (asking a user which of several
     versions is intended when the checkout-update operation is given
     an ambiguous hash prefix).

     Events for which this list will be populated:

     - FSL_CEVENT_MULTIPLE_VERSIONS: each list entry will be a (char
     const*) with a version number, branch name, or similar, perhaps
     with relevant metadata such as a checkin timestamp. The client is
     expected to pick one answer, set its list index to the
     fsl_confirm_response::multipleChoice member, and to set
     fsl_confirm_response::response to FSL_CRESPONSE_MULTI.

     In all cases, a response of FSL_CRESPONSE_CANCEL will trigger a
     cancellation.

     In all cases, the memory for the items in this list is owned by
     (or temporarily operated on the behalf of) the routine which has
     launched this query. fsl_confirm_callback_f implements must never
     manipulate the list's or its content's state.
  */
  const fsl_list * multi;
};
typedef struct fsl_confirm_detail fsl_confirm_detail;

/**
   Empty-initialized fsl_confirm_detail instance to be used for
   const-copy initialization.
*/
#define fsl_confirm_detail_empty_m \
  {FSL_CEVENT_INVALID, NULL, NULL}

/**
   Empty-initialized fsl_confirm_detail instance to be used for
   non-const-copy initialization.
*/
FSL_EXPORT const fsl_confirm_detail fsl_confirm_detail_empty;

/**
   Should present the user (if appropriate) with an option of how to
   handle the given event write that answer to
   outAnswer->response. Return 0 on success, non-0 on error, in which
   case the current operation will fail with that result code.
   Answering with FSL_CRESPONSE_CANCEL is also considered failure but
   recoverably so, whereas a non-cancel failure is considered
   unrecoverable.
*/
typedef int (*fsl_confirm_callback_f)(fsl_confirm_detail const * detail,
                                      fsl_confirm_response *outAnswer,
                                      void * confirmerState);

/**
   A fsl_confirm_callback_f and its callback state, packaged into a
   neat little struct for easy copy/replace/restore of confirmers.
*/
struct fsl_confirmer {
  /**
     Callback which can be used for basic interactive confirmation
     purposes, within the very libfossil-centric limits of the
     interface.
  */
  fsl_confirm_callback_f callback;
  /**
     Opaque state pointer for this->callback. Its lifetime is not
     managed by this object and it is assumed, if not NULL, to live at
     least as long as this object.
  */
  void * callbackState;
};
typedef struct fsl_confirmer fsl_confirmer;
/** Empty-initialized fsl_confirmer instance for const-copy
    initialization. */
#define fsl_confirmer_empty_m {NULL,NULL}
/** Empty-initialized fsl_confirmer instance for non-const-copy
    initialization. */
FSL_EXPORT const fsl_confirmer fsl_confirmer_empty;

/**
   State for use with fsl_dircrawl_f() callbacks.

   @see fsl_dircrawl()
*/
struct fsl_dircrawl_state {
  /**
     Absolute directory name of the being-visited directory.
  */
  char const *absoluteDir;
  /**
     Name (no path part) of the entry being visited.
  */
  char const *entryName;
  /**
     Filesystem entry type.
  */
  fsl_fstat_type_e entryType;
  /**
     Opaque client-specified pointer which was passed to
     fsl_dircrawl().
  */
  void * callbackState;

  /**
     Directory depth of the crawl process, starting at 1 with
     the directory passed to fsl_dircrawl().
  */
  unsigned int depth;
};
typedef struct fsl_dircrawl_state fsl_dircrawl_state;

/**
   Callback type for use with fsl_dircrawl(). It gets passed the
   absolute name of the target directory, the name of the directory
   entry (no path part), the type of the entry, and the client state
   pointer which is passed to that routine. It must return 0 on
   success or another FSL_RC_xxx value on error. Returning
   FSL_RC_BREAK will cause directory-crawling to stop without an
   error.

   All pointers in the state argument are owned by fsl_dircrawl() and
   will be invalidated as soon as the callback returns, thus they must
   be copied if they are needed for later.
*/
typedef int (*fsl_dircrawl_f)(fsl_dircrawl_state const *);

/**
   Recurses into a directory and calls a callback for each filesystem
   entry.  It does not change working directories, but callbacks are
   free to do so as long as they restore the working directory before
   returning.

   The first argument is the name of the directory to crawl. In order
   to avoid any dependence on a specific working directory, if it is
   not an absolute path then this function will expand it to an
   absolute path before crawling begins. For each entry under the
   given directory, it calls the given callback, passing it a
   fsl_dircrawl_state object holding various state. All pointers in
   that object, except for the callbackState pointer, are owned by
   this function and may be invalidated as soon as the callback
   returns.

   For each directory entry, it recurses into that directory,
   depth-first _after_ passing it to the callback.

   It DOES NOT resolve/follow symlinks, instead passing them on to the
   callback for processing. Note that passing a symlink to this
   function will not work because this function does not resolve
   symlinks. Thus it provides no way to traverse symlinks, as its
   scope is only features suited for the SCM and symlinks have no
   business being in an SCM. (Fossil supports symlinks, more or less,
   but libfossil does not.)

   It silently skips any files for which stat() fails or is not of a
   "basic" file type (e.g. character devices and such).

   Returns 0 on success, FSL_RC_TYPE if the given name is not a
   directory, and FSL_RC_RANGE if it recurses "too deep," (some
   "reasonable" internally hard-coded limit), in order to prevent a
   stack overflow.

   If the callback returns non-0, iteration stops and returns that
   result code unless the result is FSL_RC_BREAK, which stops
   iteration but causes 0 to be returned from this function.
*/
FSL_EXPORT int fsl_dircrawl(char const * dirName, fsl_dircrawl_f callback,
                            void * callbackState);

/**
   Strips any trailing slashes ('/') from the given string by
   assigning those bytes to NUL and returns the number of slashes
   NUL'd out. nameLen must be the length of the string. If nameLen is
   negative, fsl_strlen() is used to calculate its length.
*/
FSL_EXPORT fsl_size_t fsl_strip_trailing_slashes(char * name, fsl_int_t nameLen);

/**
   A convenience from of fsl_strip_trailing_slashes() which strips
   trailing slashes from the given buffer and changes its b->used
   value to account for any stripping. Results are undefined if b is
   not properly initialized.
*/
FSL_EXPORT void fsl_buffer_strip_slashes(fsl_buffer * b);

/**
   Appends each ID from the given bag to the given buffer using the given
   separator string. Returns FSL_RC_OOM on allocation error.
*/
FSL_EXPORT int fsl_id_bag_to_buffer(fsl_id_bag const * bag, fsl_buffer * b,
                                    char const * separator);

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










































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted include/fossil-scm/fossil-vpath.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/* -*- 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_VPATH_H_INCLUDED)
#define ORG_FOSSIL_SCM_FSL_VPATH_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 public APIs relating to calculating paths via
  Fossil SCM version history.
*/

#include "fossil-internal.h" /* MUST come first b/c of config macros */

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

typedef struct fsl_vpath_node fsl_vpath_node;
typedef struct fsl_vpath fsl_vpath;

/**
   Holds information for a single node in a path of checkin versions.

   @see fsl_vpath
*/
struct fsl_vpath_node {
  /** ID for this node */
  fsl_id_t rid;
  /** True if pFrom is the parent of rid */
  bool fromIsParent;
  /** True if primary side of common ancestor */
  bool isPrimary;
  /* HISTORICAL: Abbreviate output in "fossil bisect ls" */
  bool isHidden;
  /** Node this one came from. */
  fsl_vpath_node *pFrom;
  union {
    /** List of nodes of the same generation */
    fsl_vpath_node *pPeer;
    /** Next on path from beginning to end */
    fsl_vpath_node *pTo;
  } u;
  /** List of all nodes */
  fsl_vpath_node *pAll;
};

/**
   A utility type for collecting "paths" between two checkin versions.
*/
struct fsl_vpath{
  /** Current generation of nodes */
  fsl_vpath_node *pCurrent;
  /** All nodes */
  fsl_vpath_node *pAll;
  /** Nodes seen before */
  fsl_id_bag seen;
  /** Number of steps from first to last. */
  int nStep;
  /** Earliest node in the path. */
  fsl_vpath_node *pStart;
  /** Common ancestor of pStart and pEnd */
  fsl_vpath_node *pPivot;
  /** Most recent node in the path. */
  fsl_vpath_node *pEnd;
};

/**
   An empty-initialize fsl_vpath object, intended for const-copy
   initialization.
*/
#define fsl_vpath_empty_m {0,0,fsl_id_bag_empty_m,0,0,0,0}

/**
   An empty-initialize fsl_vpath object, intended for copy
   initialization.
*/
FSL_EXPORT const fsl_vpath fsl_vpath_empty;


/**
   Returns the first node in p's path.

   The returned node is owned by path may be invalidated by any APIs
   which manipulate path.
*/
FSL_EXPORT fsl_vpath_node * fsl_vpath_first(fsl_vpath *p);

/**
   Returns the last node in p's path.

   The returned node is owned by path may be invalidated by any APIs
   which manipulate path.
*/
FSL_EXPORT fsl_vpath_node * fsl_vpath_last(fsl_vpath *p);

/**
   Returns the next node p's path.

   The returned node is owned by path may be invalidated by any APIs
   which manipulate path.

   Intended to be used like this:

   @code
   for( p = fsl_vpath_first(path) ;
   p ;
   p = fsl_vpath_next(p)){
   ...
   }
   @endcode
*/
FSL_EXPORT fsl_vpath_node * fsl_vpath_next(fsl_vpath_node *p);

/**
   Returns p's path length.
*/
FSL_EXPORT int fsl_vpath_length(fsl_vpath const * p);

/**
   Frees all nodes in path (which must not be NULL) and resets all
   state in path. Does not free path.
*/
FSL_EXPORT void fsl_vpath_clear(fsl_vpath *path);

/**
   Find the mid-point of the path.  If the path contains fewer than
   2 steps, returns 0. The returned node is owned by path may be
   invalidated by any APIs which manipulate path.
*/
FSL_EXPORT fsl_vpath_node * fsl_vpath_midpoint(fsl_vpath * path);


/**
   Computes the shortest path from checkin versions iFrom to iTo
   (inclusive), storing the result state in path. If path has
   state before this is called, it is cleared by this call.

   iFrom and iTo must both be valid checkin version RIDs.

   If directOnly is true, then use only the "primary" links from
   parent to child.  In other words, ignore merges.

   On success, returns 0 and path->pStart will point to the
   beginning of the path (the iFrom node). If pStart is 0 then no
   path could be found but 0 is still returned.

   Elements of the path can be traversed like so:

   @code
   fsl_vpath path = fsl_vpath_empty;
   fsl_vpath_node * n = 0;
   int rc = fsl_vpath_shortest(f, &path, versionFrom, versionTo, 1, 0);
   if(rc) { ... error ... }
   for( n = fsl_vpath_first(&path); n; n = fsl_vpath_next(n) ){
   ...
   }
   fsl_vpath_clear(&path);
   @endcode

   On error, f's error state may be updated with a description of the
   problem.
*/
FSL_EXPORT int fsl_vpath_shortest( fsl_cx * f, fsl_vpath * path,
                                   fsl_id_t iFrom, fsl_id_t iTo,
                                   bool directOnly, bool oneWayOnly );

/**
   Reconstructs path from path->pStart to path->pEnd, reversing its
   order by fiddling with the u->pTo fields.

   Unfortunately does not reverse after the initial creation/reversal
   :/.
*/
FSL_EXPORT void fsl_vpath_reverse(fsl_vpath * path);


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


























































































































































































































































































































































































Deleted include/fossil-scm/fossil.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/* -*- 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_LIBFOSSIL_H_INCLUDED)
#define ORG_FOSSIL_SCM_LIBFOSSIL_H_INCLUDED
/*
  Copyright 2013-2021 Stephan Beal (https://wanderinghorse.net).



  This program is free software; you can redistribute it and/or
  modify it under the terms of the Simplified BSD License (also
  known as the "2-Clause License" or "FreeBSD License".)

  This program is distributed in the hope that it will be useful,
  but without any warranty; without even the implied warranty of
  merchantability or fitness for a particular purpose.

  *****************************************************************************
  This file is the primary header for the public APIs. It includes
  various other header files. They are split into multiple headers
  primarily becuase my poor old netbook is beginning to choke on
  syntax-highlighting them and browsing their (large) Doxygen output.
*/

/*
   config.h MUST be included first so we can set some portability
   flags and config-dependent typedefs!
*/
#include "fossil-config.h"
#include "fossil-util.h"
#include "fossil-core.h"
#include "fossil-db.h"
#include "fossil-repo.h"
#include "fossil-checkout.h"
#include "fossil-confdb.h"
#include "fossil-hash.h"
#include "fossil-auth.h"
#include "fossil-vpath.h"

#endif
/* ORG_FOSSIL_SCM_LIBFOSSIL_H_INCLUDED */
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


















































































Deleted include/fossil-scm/fossil.hpp.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
/* -*- 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_LIBFOSSIL_HPP_INCLUDED)
#define ORG_FOSSIL_SCM_LIBFOSSIL_HPP_INCLUDED
/**
  Copyright 2013-2021 Stephan Beal (https://wanderinghorse.net).

  Derived heavily from previous work:

  Copyright (c) 2013 D. Richard Hipp (https://www.hwaci.com/drh/)

  This program is free software; you can redistribute it and/or
  modify it under the terms of the Simplified BSD License (also
  known as the "2-Clause License" or "FreeBSD License".)

  This program is distributed in the hope that it will be useful,
  but without any warranty; without even the implied warranty of
  merchantability or fitness for a particular purpose.
*/

/*
   fossil.h MUST be included first so we can set some portability
   flags and config-dependent typedefs!
*/
#include "fossil-scm/fossil.h"
#include <stdexcept>
#include <string>
#include <sstream>
#include <stdarg.h>
#if 0
#include <cstdint> /* FIXME: use this if available (C++11) */
#endif

/**
   fsl is the primary namespace of the libfossil C++ API. The C++ API
   wraps much of the C-level function and simplifies its usage
   somewhat by using exceptions extensively for error reporting.

   A brief note about exceptions: functions and methods with do not
   throw are marked as throw(). Any others may very well throw. Though
   in practice the library APIs "simply do not fail" if used properly,
   there are gazillions of _potential_ error cases which the
   underlying C library _may_ propagate up to the client (in this case
   the C++ API), and this C++ wrapper treats almost every C-level
   error as an Exception, with only a few exceptions to simplify usage
   (e.g. cleanup-related functions never throw because they are
   generally used in destructors, and dtors are conventionally
   prohibited from throwing). The base exception type used by the
   library is fsl::Exception, which derives from std::exception per
   long-standing convention.

   While the API is not STL-centric, it does provide some basic
   STL-relatated support, e.g. the fsl::FslOutputFStream and
   fsl::BufferOStream classes.

*/
namespace fsl {

  /**
     The base exception type used by the API. It follows the libfossil
     convention of returning errors as (if possible) an error code
     from the fsl_rc_e enum and a descriptive string (which generally
     defaults to the string-form of the FSL_RC_xxx constant value (see
     fsl_rc_cstr()).
  */
  class Exception : public std::exception {
    public:
    /**
       Cleans up any error string memory.
    */
    virtual ~Exception() throw();

    /** Copies the error message state from other. */
    Exception(Exception const &other) throw();

    /** Copies the error message state from other. */
    Exception & operator=(Exception const &other) throw();

    /**
       Sets the error state from the given arguments. If msg.empty()
       then fsl_rc_cstr(code) is used as a message string.
    */
    Exception(int code, std::string const & msg) throw();

    /**
       Equivalent to Exception(code, fsl_rc_cstr(code)).
    */
    explicit Exception(int code) throw();

    /**
       A default-constructed exception with no message string
       and an error code of FSL_RC_ERROR. Intended to be used
       in conjunction with the (fsl_error*) implicit conversion
       and C APIs taking a fsl_error pointer.
     */
    Exception() throw();

    /**
       Moves err's contents into this object, effectively upgrading it
       to something we can throw. It is safe to pass a local
       stack-allocated fsl_error object provided it is properly
       initialized (via copy-construction from fsl_error_empty
       resp. fsl_error_empty_m) because this function takes its memory
       from it.
    */
    explicit Exception( fsl_error & err ) throw();

    /**
       Copies err's contents into this object, effectively upgrading
       it to something we can throw. Is generally intended to be
       passed the return value from, e.g. fsl_cx_err_get_e().
    */
    explicit Exception( fsl_error const * err ) throw();

    /**
       Sets the error state from the given code (generally assumed to
       be a fsl_rc_e value!) and a formatted string, supporting the
       same formatting options as fsl_appendf() and friends.

       As a special case, if code==FSL_RC_OOM then the message
       string is ignored to avoid allocating memory for the
       error message.

       When passing on strings from external sources, it is safest to
       pass "%s" as the format string and the foreign string as the
       first variadic argument.  That ensures that percent signs in
       the foreign input do not get processed as format specifiers
       (which expect further arguments in the variadic list, which
       will likely lead to undefined behaviour).
    */
    Exception(int code, char const * fmt, ...) throw();

    /**
       Equivalent to Exception(code,fmt,...) except that it
       takes a va_list.
    */
    Exception(int code, char const * fmt, va_list) throw();

    /**
       Returns the message string provided to the ctor.
       It's not called message() because this interface
       is inherited from std::exception.
    */
    virtual char const * what() const throw();

    /**
       Alias (for API consistency's sake) for what().
    */
    char const * messsage() const throw();

    /**
       Returns the code passed to the constructor.
    */
    int code() const throw();

    /**
       Equivalent to fsl_rc_cstr(this->code()).
    */
    char const * codeString() const throw();

    /**
       Implicit conversion to simplify some integration with
       the C APIs.
    */
    operator fsl_error * () throw();

    /**
       Const-correct overload.
    */
    operator fsl_error const * () const throw();

    private:
    fsl_error err;
    /** Internal code consolidator. */
    void error(int code, char const * fmt, va_list vargs) throw();
  };

  /**
     Out-of-memory exception.
  */
  class OOMException : public Exception{
    public:
    /**
       Sets the code FSL_RC_OOM and uses no error string (to avoid
       allocating more memory).
    */
    OOMException() throw();
  };

  /**
     A very thin varnish over ::fsl_buffer, the primary advantage
     being that it frees its memory when it destructs, so it's easier
     to keep exception-safe than a raw fsl_buffer.

     It implements an implicit conversion to (fsl_buffer*), making it
     trivial to use with the C-level fsl_buffer APIs.

     Note that the underlying buffer APIs guaranty that they
     NUL-terminates the buffer when data is appended to it, so it can
     easily be used to create dynamic strings (which is in fact one of
     its primary uses). As for strlen() and conventional string
     classes (e.g. std::string), the automatically-added NUL byte is
     never counted as part of the "effective length" of the buffer
     (Buffer::used() resp.  fsl_buffer::used).

     Example usage:

     @code
     Buffer b;
     b.appendf(b, "hi, %s!", "world"); // throws on buffer (re)allocation error
     std::cout << b << '\n';
     @endcode

  */
  class Buffer {
  private:
    fsl_buffer buf;
  public:
    /**
       Initializes the buffer. If startingSize is greater than
       zero it reserves that amount of memory, throwing an
       OOMException if that fails.
    */
    explicit Buffer(fsl_size_t startingSize);
    /**
       Initializes an empty buffer.
    */
    Buffer();
    /**
       Frees all memory owned by the buffer.
    */
    ~Buffer() throw();

    /**
       Replaces the current buffer contents with a copy of those from
       the given buffer. It re-uses the existing buffer memory if
       enough is available, and will shrink itself to fit if re-using
       a buffer would waste "too much" memory.
    */
    Buffer & operator=(Buffer const & other);

    /**
       Copies the contents of the other buffer.
    */
    Buffer(Buffer const & other);

    /**
       Implicit conversion to (fsl_buffer *) to simplify usage with
       the C API.

       NEVER EVER use/rely upon this conversion in the following
       context:

       - As a fsl_appendf() (or similar) argument when using the %b/%B
       (fsl_buffer-specific) format specifiers. Use handle()
       instead or the wrong conversion will be used because
       compile-time doesn't know this conversion should be used at
       that point (leading to undefined results).
    */
    operator fsl_buffer * () throw();

    /**
       Const-correct overload.
    */
    operator fsl_buffer const * () const throw();

    /**
       Returns the underlying fsl_buffer this object proxies.
       See operator fsl_buffer *().
    */
    fsl_buffer * handle() throw();

    /**
       Const-correct overload.
    */
    fsl_buffer const * handle() const throw();

    /**
       Returns true if 0==used().
    */
    bool empty() const throw();

    /**
       Returns the "used" number of bytes in the buffer.
    */
    fsl_size_t used() const throw();

    /**
       Returns the current capacity of the buffer.
    */
    fsl_size_t capacity() const throw();

    /**
       Returns a pointer to the current buffer, which points to
       used() bytes of memory.  Returns NULL for an empty()
       buffer. The returned pointer may be invalidated on any changes
       to the buffer.
    */
    unsigned char * mem() throw();

    /**
       Const-correct overload.
    */
    unsigned char const * mem() const throw();

    /**
       Equivalent to fsl_buffer_clear(*this).
    */
    Buffer & clear() throw();

    /**
       Equivalent to fsl_buffer_reset(*this).
    */
    Buffer & reset() throw();

    /**
       Equivalent to fsl_buffer_reserve(*this, n), but throws on error
       and returns this object on success.
    */
    Buffer & reserve(fsl_size_t n);

    /**
       Equivalent to fsl_buffer_resize(*this, n), but throws on error
       and returns this object on success.
    */
    Buffer & resize(fsl_size_t n);

    /**
       STL-style iterator for the buffer's memory.
    */
    typedef unsigned char * iterator;

    /**
       STL-style const iterator for the buffer's memory.
    */
    typedef unsigned char const * const_iterator;

    /**
       Returns the starting memory position iterator,
       or NULL if empty(). It may be invalidated by
       any changes to the buffer.
    */
    iterator begin() throw();

    /**
       Returns the one-after-the-end of the memory position iterator
       (the "used" space, not necessarily the capacity), or NULL if
       empty(). It may be invalidated by any changes to the buffer.
    */
    iterator end() throw();

    /**
       Const-correct overload.
    */
    const_iterator begin() const throw();

    /**
       Const-correct overload.
    */
    const_iterator end() const throw();

    /**
       Basically equivalent to fsl_buffer_appendf(*this,...)
       except that it throws if that function fails.
    */
    Buffer & appendf(char const * fmt, ...);

    /**
       Returns a pointer to the buffer member. It may be
       invalidated by any changes to the buffer. Returns
       NULL if the buffer has never has any contents,
       but after that this will return an empty string if
       the buffer is empty.
    */
    char const * c_str() const throw();

    /**
       Throws an Exception using the given error code and the contents
       of the buffer as the message.
    */
    void toss(int errorCode) const;
  };

  /**
     Writes the first b.used() bytes of b.mem() to os and returns os.
  */
  std::ostream & operator<<( std::ostream & os, Buffer const & b );

  /**
     Accepts any type compatible with std::ostream<<v, converts
     it to a string (via std::ostringstream), then appends
     that result to b. Returns b.

  */
  template <typename ValueType>
  Buffer & operator<<( Buffer & b, ValueType const v ){
    std::ostringstream os;
    os << v;
    std::string const & s(os.str());
    if(!s.empty()){
      int const rc = fsl_buffer_append(b, s.data(),
                                       (fsl_size_t)s.size());
      if(rc) throw Exception(rc);
    }
    return b;
  }

  class Db;
  /**
     A prepared statement, the C++ counterpart to ::fsl_stmt.

     The vast majority of the members (those not marked with throw())
     throw an Exception on error.

     Sample usage:

     @code
     Stmt st(myDb);
     st.prepare("SELECT blah FROM foo WHERE id=?")
         .bind(1,42)
         .eachRow( MyStepFunctor() )
         .finalize();
     @endcode

     While they are normally created on the stack, they may live on
     the heap as long as they do not outlive their database.
  */
  class Stmt {
  public:
    /**
       Sets up initial state. Most of the member functions will throw
       until this statement is prepared (via Db::prepare()).
    */
    explicit Stmt(Db & db) throw();
    /**
       Frees any underlying resources.
     */
    ~Stmt() throw();

    /**
       Implicit conversion to (fsl_stmt *) to simplify usage with
       the C API.

       ABSOLUTELY DO NOT:

       - ... use this conversion to pass this object to
       fsl_stmt_finalize()!!!  Doing so will lead to a dangling
       pointer and an eventual segfault and/or double-free().

       - ... expect this conversion to be picked up when a function
       takes a void pointer argument.
    */
    operator fsl_stmt * () throw();

    /**
       Const-correct overload.
    */
    operator fsl_stmt const * () const throw();

    /**
       Basically a proxy for fsl_db_prepare(), this routine sets this
       object's statement up from the given formatted SQL string. See
       fsl_appendf() for the supported formatting specifiers.

       When passing on strings from external sources, it is safest to
       pass "%s" as the format string and the foreign string as the
       first variadic argument.  That ensures that percent signs in
       the foreign input do not get processed as format specifiers
       (which expect further arguments in the variadic list, which
       will likely lead to undefined behaviour).

       Throws on error. Returns this object.
    */
    Stmt & prepare(char const * sql, ... );

    /**
       Equivalent to this->prepare(db, "%s", sql.c_str()).
    */
    Stmt & prepare(std::string const & sql);

    /**
       Equivalent to this->prepare(db, "%s", sql.c_str()).
     */
    Stmt & prepare(Buffer const & sql);

    /**
       Steps one row. Returns true if it fetches a row, false at the
       end of the result set (and for non-fetching queries like
       INSERT/UPDATE/DELETE), and throws on error.
    */
    bool step();

    /**
       step()s the statement one time and throws if the step() call
       does _not_ return false. This is intended for stepping inserts,
       updates, and other non-fetching queries. Throws on error,
       returns this object on success.
    */
    Stmt & stepExpectDone();

    /**
       "Resets" the statement so it can be executed again, as per
       fsl_stmt_reset(). If resetStepCounterToo is true then the
       step-counter is also reset to 0 (see fsl_stmt_reset2() and
       stepCount()).

       Returns this object.
    */
    Stmt & reset(bool resetStepCounterToo = false);

    /**
       Frees any underlying resources owned by this statement.  It can
       be prepared() again after calling this.
    */
    Stmt & finalize() throw();

    /**
       Returns the number of times step() has fetched a row
       of data since this counter was last reset.
    */
    int stepCount() const throw();

    /**
       Returns the number of bound parameters in the statement.
    */
    int paramCount() const throw();

    /**
       Returns the number of "fetchable" columns in the statement.
    */
    int columnCount() const throw();

    /**
       Returns the SQL of the statement, or NULL if the statement is
       not prepared.
    */
    char const * sql() const throw();

    /**
       Returns this object's C-level fsl_stmt handle.

       Provided so that client APIs can use fsl_stmt_xxx() functions
       not wrapped by the C++ APIs. DO NOT, under ANY CIRCUMSTANCES,
       delete, free, fsl_stmt_finalize(), nor otherwise mess with this
       handle's ownership. It is owned by this object.

       Returns NULL if not yet prepared.
    */
    fsl_stmt * handle() throw();

    /**
       Const-correct overload.
    */
    fsl_stmt const * handle() const throw();

    /** Analog to fsl_stmt_g_int64() */
    int32_t getInt32(short colZeroBased);

    /** Analog to fsl_stmt_g_int64() */
    int64_t getInt64(short colZeroBased);

    /** Analog to fsl_stmt_g_id() */
    fsl_id_t getId(short colZeroBased);

    /** Analog to fsl_stmt_g_double() */
    double getDouble(short colZeroBased);

    /** Analog to fsl_stmt_g_text() */
    char const * getText(short colZeroBased, fsl_size_t * length = NULL);

    /** Analog to fsl_stmt_g_blob() */
    void const * getBlob(short colZeroBased, fsl_size_t * length = NULL);

    /** Analog to fsl_stmt_col_name() */
    char const * columnName(short colZeroBased);

    /** Analog to fsl_stmt_param_index() */
    int paramIndex(char const * name);

    /** Analog to fsl_stmt_bind_null() */
    Stmt & bind(short colOneBased);

    /** Analog to fsl_stmt_bind_int32() */
    Stmt & bind(short colOneBased, int32_t v);

    /** Analog to fsl_stmt_bind_int64() */
    Stmt & bind(short colOneBased, int64_t v);

    /** Analog to fsl_stmt_bind_int64() */
    Stmt & bind(short colOneBased, double v);

    /** Analog to fsl_stmt_bind_text() */
    Stmt & bind(short colOneBased, char const * str,
                fsl_int_t len = -1, bool copyBytes = true);

    /** Equivalent to bind(colOneBased, str.c_str(), str.size()); */
    Stmt & bind(short colOneBased, std::string const & str);

    /** Analog to fsl_stmt_bind_blob() */
    Stmt & bind(short colOneBased, void const * blob, fsl_size_t len,
                bool copyBytes = true);

    /** Analog to fsl_stmt_bind_null_name() */
    Stmt & bind(char const * col);

    /** Analog to fsl_stmt_bind_int32_name() */
    Stmt & bind(char const * col, int32_t v);

    /** Analog to fsl_stmt_bind_int64_name() */
    Stmt & bind(char const * col, int64_t v);

    /** Analog to fsl_stmt_bind_double_name() */
    Stmt & bind(char const * col, double v);

    /** Analog to fsl_stmt_bind_text_name() */
    Stmt & bind(char const * col, char const * str,
                fsl_int_t len = -1, bool copyBytes = true);

    /** Equivalent to bind(col, str.c_str(), str.size()); */
    Stmt & bind(char const * col, std::string const & str);

    /** Analog to fsl_stmt_bind_blob_name() */
    Stmt & bind(char const * col, void const * blob,
                fsl_size_t len, bool copyBytes = true);

    /**
       Runs a loop over func(*this), calling it once for each time
       this->step() returns true.
    */
    template <typename Func>
    Stmt & eachRow( Func const & func ){
      while(this->step()){
        func(*this);
      }
      return *this;
    }

    /**
       Runs a loop over func(*this, state), calling it once for each
       time this->step() returns true.
    */
    template <typename State, typename Func>
    Stmt & eachRow( Func const & func, State & state ){
      while(this->step()){
        func(*this, state);
      }
      return *this;
    }

    /**
       Binds each entry of the input iterator range [begin,end),
       starting at index 1 and moving up.
    */
    template <typename IteratorT>
    Stmt & bindIter( IteratorT const & begin, IteratorT const & end ){
      IteratorT it(begin);
      short col = 0;
      for( ; it != end; ++it ){
        this->bind( ++col, *it );
      }
      return *this;
    }
    /**
       Binds each entry of the given std::list-like type. Each entry
       in the list is bound starting at index 1 and moving up.
    */
    template <typename ListType>
    Stmt & bindList( ListType const & li ){
      return bindIter( li.begin(), li.end() );
    }

    /**
       Runs a loop over func(this->handle(), &state), calling it
       once for each time this->step() returns true. If func() returns
       0 the loop continues. If it returns FSL_RC_BREAK then the loop
       stops without an error. If some other value is returned an
       exception is thrown. Basically a templated form of
       fsl_stmt_each().
    */
    template <typename State>
    Stmt & eachRow( fsl_stmt_each_f func, State & state ){
      int rc = 0;
      while(this->step()){
        rc = func(&this->stmt, &state);
        switch(rc){
          case 0: continue;
          case FSL_RC_BREAK: break;
          default:
            this->propagateError();
            throw Exception(rc, "Statement eachRow() callback says: %s\n",
                            fsl_rc_cstr(rc));
        }
      }
      return *this;
    }

  private:
    Db & db;
    fsl_stmt stmt;
    friend class Db;
    /** Not implemented. Copying not allowed. */
    Stmt(Stmt const &);
    /** Not implemented. Copying not allowed. */
    Stmt & operator=(Stmt const &);

    /**
       Throws an Exception if col is not in the legal bind/get column
       range. base is the counting index (0 for "get", 1 for
       "bind"). It must be 0 or 1.
    */
    void assertRange(short col, short base) const;
    /**
       If rc is not 0 is throws an Exception. If rc==the error
       code of the underlying db handle then that error info
       is propagated.
    */
    void assertRC(char const * context, int rc) const;

    /**
       If this->stmt.db is not NULL and this->stmt.db->error.code is
       not 0 then it gets propagated as an Exception, else this is a
       no-op.
    */
    void propagateError() const;

    /**
       Throws a FSL_RC_MISUSE excception if this statement has not
       been prepared.
    */
    void assertPrepared() const;
  };// Stmt class

  /**
     A utility class for binding values to statements.

     Example usage:

     @code
     // Assume we have an INSERT or UPDATE statement
     // with 3 columns to bind:
     StmtBinder b(stmt);
     b
         (value1)(value2)(value3)
         .insert()
         (42)("hi")(someBlob, blobSize)
         .insert()
         ;
     @endcode
  */
  class StmtBinder{
  private:
    Stmt & st;
    short col;
  public:
    StmtBinder(Stmt &s);
    ~StmtBinder();

    /**
       Equivalent to stmt().bind(v,len,copyBytes),
       except that it returns this object.
     */
    StmtBinder & operator()(char const * v, fsl_int_t len = -1,
                            bool copyBytes = true);

    /**
       Equivalent to stmt().bind(v,len,copyBytes),
       except that it returns this object.
    */
    StmtBinder & operator()(void const * v, fsl_size_t len,
                            bool copyBytes = true);

    /**
       A generic bind() op which simply calls stmt().bind(X, v), where
       X is the next bind column for this object. Throws if the column
       is out of range or binding fails.

       Returns this object.
    */
    StmtBinder & operator()();
    template <typename ValT>
    StmtBinder & operator()(ValT v){
      st.bind(++this->col, v);
      return *this;
    }

    template <typename ListType>
    StmtBinder & bindList( ListType const & li ){
      this->st.bindList(li);
      return *this;
    }


    /**
       Resets this object's bind column counter. Calls stmt().reset()
       if alsoStatement is true. Returns this object.
    */
    StmtBinder & reset(bool alsoStatement = true);

    /**
       Returns the underlying statement handle.
    */
    Stmt & stmt();

    /**
       Equivalent to stmt().step().
    */
    bool step();

    /**
       Calls stepExpectDone() on the underlying statement, calls
       this->reset(), and returns this object. Can be used for any
       type of statement which is expected to trigger an
       FSL_RC_STEP_DONE from the underlying fsl_stmt API. Basically
       this means INSERT, UPDATE, REPLACE, and DELETE statements.

       The convenience factor of this function is that
       stepExpectDone() will throw if fsl_stmt_step()ing the
       underlying statement does _not_ return FSL_RC_STEP_DONE.
    */
    StmtBinder & once();

    /** Alias for once(), for readability. */
    StmtBinder & insert(){return this->once();}
    /** Alias for once(), for readability. */
    StmtBinder & update(){return this->once();}
  };

  /**
     C++ counterpart to ::fsl_db.

     The vast majority of the members (those not marked with throw())
     throw an Exception on error.
  */
  class Db {
    public:
    /**
       Initializes internal state for a closed db.  Use open() to open
       it or handle() to import a foreign fsl_db handle.
    */
    Db();

    /**
       Initializes internal state and calls Calls
       this->open(filename,openFlags).
    */
    Db(char const * filename, int openFlags);

    /**
       Calls this->close().
    */
    virtual ~Db() throw();

    /**
       Counterpart of fsl_db_open(), with the same semantics for the
       arguments, except that this function throws on error.

       Throws on error, else returns this object.

       Throws if the db is currently opened.
    */
    Db & open(char const * filename, int openFlags);


    /**
       Implicit conversion to (fsl_db *) to simplify usage with
       the C API.

       ABSOLUTELY DO NOT:

       - ... use this conversion to pass this object to
       fsl_db_close()!!!  Doing so will lead to a dangling pointer and
       an eventual segfault and/or double-free().

       - ... expect this conversion to be picked up when a function
       takes a void pointer argument.
    */
    operator fsl_db * () throw();

    /**
       Const-correct overload.
    */
    operator fsl_db const * () const throw();

    /**
       If this->ownsHandle() is true and this object has an opened db,
       it fsl_db_close() that db, (possibly) freeing the handle and
       (definitely) any db-owned resources.  If !this->ownsHandle()
       this this clears this object's reference to the underlying db
       but does _not_ fsl_db_close() it.  If this object has no Db
       handle then this is a harmless no-op.
    */
    Db & close() throw();

    /**
       Calls this->close() and replaces the internal db handle with
       the given one.

       If ownsHandle is true then this object takes over ownership of
       db. It is CRITICAL that there never be more than one owner (and
       that owner may be at the C level, unaware of this class). It is
       also critical that the "owning" Db instance (or exteran fsl_db
       handle not associated with an owning Db instance) outlive any
       shared Db instances using that handle. (We can't currently
       ensure that with a shared pointer due to C-level ownership
       internals.)

       If ownsHandle is false then this object becomes a proxy for the
       given handle but will never close the handle - its memory is
       assumed to live somewhere else and the fsl_db instance MUST
       OUTLIVE THIS OBJECT.

       It is legal to pass NULL db, which triggers a close() and then
       clears the Db handle. In that case, this object will create a
       new handle if open() is called on it while it has none.

       As a special case, if this->handle()==db, this is a no-op.

       The primarily intention of this mechanism is so that
       fsl::Context, which proxies up to three db handles, can do so
       without having to fight the C-level API for ownership of those
       handles.
    */
    Db & handle( fsl_db * db, bool ownsHandle ) throw();

    /**
       Returns the filename used to open the DB or NULL
       if it is not opened.
    */
    char const * filename() throw();

    /**
       Returns true if this object has an opened db handle,
       else false.
    */
    bool isOpened() const throw();

    /**
       Returns this object's C-level fsl_db handle.

       Provided so that client APIs can use fsl_db_xxx() functions
       not wrapped by the C++ APIs. DO NOT, under ANY CIRCUMSTANCES,
       delete, free, fsl_db_close(), nor otherwise mess with this
       handle's ownership. It is owned by this object.

       Returns NULL if not yet prepared.
    */
    fsl_db * handle() throw();

    /**
       Const-correct overload.
    */
    fsl_db const * handle() const throw();

    /**
       Returns true if this object owns its underlying db handle, else
       false. Note that it may legally return true even when
       handle() returns NULL, meaning that this object is prepared
       to create a handle of its own if needed.
    */
    bool ownsHandle() const throw();

    /**
       Pushes a level onto the pseudo-recursive transaction stack. See
       fsl_db_transaction_begin().

       Throws on error. Returns this object on success.

       DO NOT EVER use transactions in the API without this function.
       For example, NEVER exec("BEGIN") because that bypasses the
       recursive transaction support.

       For an exception-safer alternative to managing transaction
       lifetimes, see the Db::Transaction helper class.
    */
    Db & begin();

    /**
       Pops one level from the transaction stack as "successful,"
       commiting the transaction only once all levels have been
       popped.

       See fsl_db_transaction_commit().

       Throws on error. Returns this object on success.

       DO NOT EVER commit a transaction without this function.  For
       example, NEVER exec("COMMIT") because that bypasses the
       recursive transaction support.

    */
    Db & commit();

    /**
       Pops one level from the transaction stack and marks the whole
       transaction as failed. It will not actually roll back the
       transaction until all levels have been popped, but further
       commit() calls will implicitly fail until the top-most
       transaction level is committed, at which point it will
       recognize the failure and perform a rollback instead.

       See fsl_db_transaction_rollback().

       Throws on error. Returns this object on success.

       DO NOT EVER roll back a transaction without this function.  For
       example, NEVER exec("ROLLBACK") because that bypasses the
       recursive transaction support.
    */
    Db & rollback() throw();

    /**
       Executes the given single fsl_appendf()-formatted SQL
       statement. See fsl_appendf() for the formatting options.

       Throws on error. Returns this object.

       See Stmt::prepare() for notes about how to sanely deal with
       unsanitized SQL strings from foreign sources.
    */
    Db & exec(char const * sql, ...);

    /**
       Equivalent to this->exec("%s", sql.c_str()).
    */
    Db & exec(std::string const & sql);

    /**
       Executes one or more SQL statements provided in the
       fsl_appendf()-formatted SQL string.  See fsl_db_exec_multi()
       for details. This can be used to import whole schemas at once,
       for example.

       Throws on error. Returns this object.
    */
    Db & execMulti(char const * sql,...);

    /**
       Equivalent to this->execMulti("%s", sql.c_str()).
    */
    Db & execMulti(std::string const & sql);

    /** Analog to fsl_db_attach(), but throws on error. */
    Db & attach(char const * filename, char const * label);

    /** Analog to fsl_db_detach(), but throws on error. */
    Db & detach(char const * label);

    /**
       Returns the current number of transactions pushed onto the
       transaction stack. If this is greater than 0 then a transaction
       is active (though it might have a failure from a rollback()
       performed higher up in the stack).
    */
    int transactionLevel() const throw();

    /**
       A utility to simplify db transaction lifetimes.

       Sample usage:

       @code
       Db::Transaction tr(db);
       ...lots of stuff which might throw...
       tr.commit();
       @endcode

       If commit() is not called, the transaction will be rolled back
       then the Transaction instance destructs.
    */
    class Transaction {
    private:
      Db & db;
      int inTrans;
      //! Not copyable.
      Transaction & operator=(Transaction const &);
      //! Not copyable.
      Transaction(Transaction const &);
    public:

      /**
         Calls db.begin().
      */
      Transaction(Db & db);

      /**
         If neither commit() nor rollback() have been called,
         this calls rollback(), otherwise it does nothing.
      */
      ~Transaction() throw();

      /**
         Commits the transaction started by the ctor.
      */
      void commit();

      /**
         Rolls back the transaction started by the ctor.
      */
      void rollback() throw();

      /**
         Returns the current transaction level for the underlying db.
      */
      int level() const throw();
    };


    private:

    friend class Stmt;

    /**
       Only mutable so that we can steal db.error's contents when
       propagating exceptions. If it's not mutable then we have to
       copy that state at throw-time.

       FIXME: we really need a weak pointer and/or a shared ptr with a
       configurable finalizer.
    */
    mutable fsl_db * db;
    /**
       Whether or not this instance owns this->db.
    */
    bool ownsDb;

    /**
       Not implemented - copying not allowed.
    */
    Db(Db const &);

    /**
       Not implemented - copying not allowed.
    */
    Db & operator=(Db const &);

    void setup();

    /** Throws if rc is not 0. */
    void assertRC(char const * context, int rc) const;

    /** Throws if the db is not opened. */
    void assertOpened() const;

    /**
       Throws db->error state if db is opened and db->error.code is
       not 0, otherwise this is a no-op.
    */
    void propagateError() const;
  };

  /**
     C++ counterpart of ::fsl_cx, but generally requires less code to
     use because it throws exceptions for any notable errors.

     Example:

     @code
     Context cx;
     cx.openCheckout();

     fsl_uuid_cstr uuid = NULL;
     fsl_id_it rid = 0;
     fsl_ckout_version_info( cx, &rid, &uuid );
     // ^^^ note, that's a C function!
     FslOutputFStream os(cx);
     os << "Checkout version: "<<rid<< ' ' << uuid << '\n';
     @endcode

     The implicit conversion to ::fsl_cx makes it possible to pass
     instances to any C function taking such an argument (and legal to
     do so for most function).
  */
  class Context{
    public:
    /**
       Initializes a new fsl_cx instance, owned by this object, using
       the default initialization options.

       Throws on error.
    */
    Context();

    /**
       Initializes a new fsl_cx instance, owned by this object, using
       the given initialization options.

       Throws on error.
    */
    explicit Context(fsl_cx_init_opt const & opt);

    /**
       Initializes this object as a wrapper of the given initialized
       (via fsl_cx_init()) fsl_cx handle. If ownsHandle is true then
       ownership of f is transfered to this object. If ownsHandle is
       false then this object is just a "thin" proxy for f and f MUST
       OUTLIVE THIS OBJECT.
    */
    Context(fsl_cx * f, bool ownsHandle);

    /**
       If this object owns its context handle, it fsl_cx_finalize()s
       it, otherwise it does nothing.

       Note that this is not virtual. It's not expected that
       subclassing will be all that useful for this class.
    */
    ~Context();

    /**
       Implicit conversion to (fsl_cx *) to simplify usage with
       the C API.

       ABSOLUTELY DO NOT use this conversion...

       - ... to pass this object to fsl_cx_finalize()!  Doing so will
       lead to a dangling pointer and an eventual segfault and/or
       double-free().

       - ... with fsl_ckout_close() or fsl_repo_close() because
       pointer ownership may get confused. Use closeDbs() instead.  It
       will appear to work at times, but certain combinations of
       operations via the C API (e.g. opening a checkout, closing it,
       then opening a standalone repo) might get some C++-side pointers
       cross-wired (theoretically/hypothetically).

       - ... expect this conversion to be picked up when a function
       takes a void pointer argument.
    */
    operator fsl_cx * () throw();

    /**
       Const-correct overload.
    */
    operator fsl_cx const * () const throw();

    /**
       Returns this object's C-level fsl_cx handle.

       See operator fsl_cx *() for details.
    */
    fsl_cx * handle() throw();

    /**
       Returns true if this object owns its underlying db handle, else
       false. Note that it may legally return true even when
       handle() returns NULL, meaning that this object is prepared
       to create a handle of its own if needed.
    */
    bool ownsHandle() const throw();

    /**
       Const-correct overload.
    */
    fsl_cx const * handle() const throw();

    /**
       Counterpart of fsl_ckout_open_dir(). Throws on error,
       returns this object on success.
    */
    Context & openCheckout( char const * dirName = NULL );

    /**

     */
    Context & openRepo( char const * dbFile );

    /**
       Closes any opened repo/checkout/config databases. ACHTUNG: this
       may invalidate any Db handles pointing to them!

       Returns this object.

       ACHTUNG: because of ownership issues, clients must always use
       this function, instead of the C APIs, for closing repositories
       and checkouts.
    */
    Context & closeDbs() throw();

    /**
       Like fsl_rid_to_uuid(*this, rid), but returns the result as a
       std::string and throws on error or if no entry is found.
    */
    std::string ridToUuid(fsl_id_t rid);

    /**
       Like fsl_rid_to_artifact_uuid(*this, rid, type), but returns
       the result as a std::string and throws on error or if no entry
       is found.
    */
    std::string ridToArtifactUuid(fsl_id_t rid,
                                  fsl_satype_e type = FSL_SATYPE_ANY);

    /**
       Like fsl_sym_to_rid(*this,symbolicName), but throws on error
       or if no ID is found.
    */
    fsl_id_t symToRid(char const * symbolicName,
                      fsl_satype_e type = FSL_SATYPE_ANY);

    /**
       Equivalent to symToRid(symbolicName.c_str(), type);
    */
    fsl_id_t symToRid(std::string const & symbolicName,
                      fsl_satype_e type = FSL_SATYPE_ANY);

    /**
       Like fsl_sym_to_uuid(*this,...), but returns the result as a
       std::string and throws on error or if no entry is found. If rid
       is not NULL then on success the RID corresponding to the
       returned UUID is returned via *rid.
    */
    std::string symToUuid(char const * symbolicName,
                          fsl_id_t * rid = NULL,
                          fsl_satype_e type = FSL_SATYPE_ANY);

    /*Context & handle( fsl_db * db, bool ownsHandle ) throw();*/

    /**
       This returns a handle to the repository database. It might
       not be opened. The handle and its db connection are
       owned by this object resp. by lower levels of the API.
    */
    Db & dbRepo() throw();

    /**
       This returns a handle to the checkout database. It might
       not be opened. The handle and its db connection are
       owned by this object resp. by lower levels of the API.

       Note that if a checkout is opened, an repo will also be opened,
       but not necessarily the other way around.
    */
    Db & dbCheckout() throw();

    /**
       This returns a handle to the context's "main" database. It
       might not be opened. The handle and its db connection are owned
       by this object resp. by lower levels of the API.
    */
    Db & db() throw();

    /**
       A utility class for managing transactions for a Context-managed
       database (regardless of whether the checkout or repo db).
     */
    class Transaction {
      private:
      Db::Transaction tr;
      int level;
    public:
      /**
         Starts a transaction in cx.db(). Throws on error.
      */
      Transaction(Context &cx);
      /**
         If commit() has not been called, this rolls back the
         transaction, otherwise it does nothing.
       */
      ~Transaction() throw();
      /**
         Commits the transaction resp. pushes this level of the
         transaction stack off the stack.
      */
      void commit();
    };

    /**
       Analog to fsl_content_get(), but throws an error and returns
       this object.
    */
    Context & getContent( fsl_id_t rid, Buffer & dest );

    /**
       Analog to fsl_content_get_sym(), but throws an error and returns
       this object.
    */
    Context & getContent( char const * sym, Buffer & dest );

    /**
       Equivalent to getContent(sym.c_str(), dest).
    */
    Context & getContent( std::string const & sym, Buffer & dest );

    private:
    /**
       FIXME: we really need a weak pointer and/or a shared ptr with a
       configurable finalizer.
     */
    fsl_cx * f;
    bool ownsCx;
    Db dbCkout;
    Db dbRe;
    Db dbMain;
    /**
       Not implemented - copying not allowed.
    */
    Context(Context const &);
    /**
       Not implemented - copying not allowed.
    */
    Context & operator=(Context const &);

    void assertRC(char const * context, int rc) const;
    void assertHasRepo();
    void assertHasCheckout();
    void propagateError() const;

    void setup(fsl_cx_init_opt const * opt);
  };


  /**
     A utility class for iterating over fsl_list instances.

     VT must be a pointer-qualified type because fsl_list only holds
     arrays of pointers.

     Modification of the list invalidates any active iterators
     (semantically, but not technically, so be careful!).

     Example usage:

     @code
     typedef FslListIterator<char const *> Iter;
     // assume d is a fsl_deck instance.
     Iter it(d.P);
     Iter end;
     for( ; it != end; ++it ){
       char const * str = *it;
       if(str){...}
     }
     @endcode

     Note that this is not type-safe per se - it relies on the
     underlying list having only entries of the given type or NULL.

     When used in conjunction with the various fsl_list members of
     fsl_deck, it is important to remember that traversing over
     fsl_deck::F.list will often, but not always, behave much
     differently then FCardIterator because this class traverses only
     the F-cards found directly in that deck, which for delta
     manifests is only a small fraction of the F-cards actually in
     that version (the rest are inherited from its baseline
     manifest). FCardIterator is generally the right way to traverse
     the F-cards (though this approach has its uses as well).
  */
  template <typename VT>
  class FslListIterator{
  private:
    fsl_list const *li;
    fsl_int_t cursor;
    VT current;
  protected:
    VT currentValue() const throw(){
      return this->current;
    }
  public:
    //! STL-compatible value_type typedef.
    typedef VT value_type;
    //! API-conventional ValueType.
    typedef VT ValueType;

    /**
       Initializes this iterator to point to the first item im list
       (if list.used>0) or to be equivalent to the end iterator (if
       the list is empty).

       Changes to the list semantically invalidate all iterators, and
       using them afterwars invokes undefined behaviour.
    */
    explicit FslListIterator(fsl_list const &list)
    : li(&list), cursor(-1), current(NULL){
      if(li->used){
        current = static_cast<value_type>(li->list[0]);
        cursor = 1;
      }
    }

    /**
       Initializes an end iterator.
     */
    FslListIterator()
      : li(NULL), cursor(-1), current(NULL)
    {}

    /**
       Increments the iterator to point at the next list entry. Throws
       if called on an end iterator or if called after the end of the
       list has been reached.
    */
    FslListIterator & operator++(){
      if((cursor<0) || ((fsl_size_t)cursor>li->used)){
        throw Exception(FSL_RC_RANGE,
                        "Cannot increment past end of list.");
      }else if(li->used==(fsl_size_t)cursor){
        current = NULL;
        cursor = -1;
      }else{
        current = static_cast<value_type>(li->list[cursor++]);
      }
      return *this;
    }

    /**
       Returns the current element's value (possibly NULL!). Throws
       for an end iterator.
    */
    ValueType operator*(){
      if(cursor<0){
        throw Exception(FSL_RC_RANGE,
                        "Invalid iterator dereference.");
      }
      return current;
    }

    bool operator==(FslListIterator const &rhs) const throw(){
      return (this->cursor==rhs.cursor);
    }

    bool operator!=(FslListIterator const &rhs) const throw(){
      return (this->cursor!=rhs.cursor);
    }
  };

  /**
     The C++ counterpart to the C-side fsl_deck class.

     Fossil's core metadata syntax calls the entries of the metadata
     "cards." A Deck (or fsl_deck) is a "collection of cards" which
     make up an atomic unit of metadata. In Fossil jargon a deck is
     called an "artifact," but libfossil adopted the name "deck"
     because "artifact" already has several meanings in this context.
  */
  class Deck{
  public:
    /**
       If this instance owns its underlying handle then this cleans up
       all resources owned by this instance, otherwise it does
       nothing.
    */
    ~Deck() throw();

    /**
       Initializes the deck using the given context. The second
       argument is only important when constructing decks, not
       when loading them.
     */
    explicit Deck(Context & cx, fsl_satype_e type = FSL_SATYPE_ANY);

    /**
       Makes this object a wrapper for d. If ownsHandle is true then
       this object takes over ownership of d, otherwise d is assumed
       to be owned elsewhere and it _must_ outlive this object.
    */
    Deck(Context & cx, fsl_deck * d, bool ownsHandle);

    /**
       Implicit conversion to (fsl_deck *) to simplify integration
       with the C API.

       ABSOLUTELY DO NOT use this conversion...

       - ... with fsl_deck_finalize(), as that may (depending on usage)
       steal a pointer out from under C++.

       - ... expect this conversion to be picked up when a function
       takes a void pointer argument.
    */
    operator fsl_deck *() throw();

    /**
       Const-correct overload.
     */
    operator fsl_deck const *() const throw();

    /**
       See operator fsl_deck*().
    */
    fsl_deck * handle() throw();

    /**
       Const-correct overload.
    */
    fsl_deck const * handle() const throw();

    /**
       If this deck was load()ed, returns the loaded artifact's type,
       else returns the type set in the constructor.
    */
    fsl_satype_e type() const throw();

    /**
       If this deck was load()ed, returns the blob.rid
       value, else returns 0.
    */
    fsl_id_t rid() const throw();

    /**
       If this deck was load()ed, returns the UUID string,
       else returns NULL. The bytes are owned by this
       object and may be invalidated by load().
    */
    fsl_uuid_cstr uuid() const throw();

    /**
       Analog to fsl_deck_has_required_cards().
    */
    bool hasAllRequiredCards() const throw();

    /**
       Analog to fsl_deck_required_cards_check(),
       but throws if that fails.
    */
    Deck const & assertHasRequiredCards() const;

    /**
       Analog to fsl_card_is_legal(), passing this->type() as the
       first argument to that function.
    */
    bool cardIsLegal(char cardLetter) const throw();

    /**
       Analog to fsl_deck_load_sym(), but throws on error. Populates this
       object with the loaded state.
    */
    Deck & load( fsl_id_t rid, fsl_satype_e type = FSL_SATYPE_ANY );

    /**
       Analog to fsl_deck_load_sym(), but throws on error. Populates this
       object with the loaded state.
    */
    Deck & load( char const * symbolicName, fsl_satype_e type = FSL_SATYPE_ANY );

    /**
       Equivalent to load(symbolicName.c_str(), type).
    */
    Deck & load( std::string const & symbolicName, fsl_satype_e type = FSL_SATYPE_ANY );

    /**
       This deck's Fossil Context.
    */
    Context & context() throw();

    /**
       Const-correct overload.
    */
    Context const & context() const throw() ;

    /**
       Equivalent to fsl_deck_clean(*this).
    */
    Deck & cleanup() throw();

    /**
       Analog to fsl_deck_output(), sending its output to the given
       stream. Throws on error and returns this object on success.
    */
    Deck const & output( std::ostream & os ) const;

    /**
       Analog to fsl_deck_output(), but throws on error
       and returns this object on success.
    */
    Deck const & output( fsl_output_f f, void * outState ) const;

    /**
       Analog to fsl_deck_save(), but throws on error and returns this
       object on success.
    */
    Deck & save(bool isPrivate = false);

    /**
       Analog to fsl_deck_unshuffle(), but throws on error and returns
       this object on success.

       Reminder: this is only necessary when using output().
    */
    Deck & unshuffle(bool calcRCard = true);

    /**
       Analog to fsl_deck_A_set() but throws on error and returns this
       object on success.
    */
    Deck & setCardA( char const * name,
                     char const * tgt,
                     fsl_uuid_cstr uuid );

    /**
       Analog to fsl_deck_B_set() but throws on error and returns this
       object on success. This destroys any object previously returned
       by baseline().
    */
    Deck & setCardB(fsl_uuid_cstr uuid);

    /**
       Analog to fsl_deck_C_set() but throws on error and returns this
       object on success.
    */
    Deck & setCardC( char const * comment );

    /**
       Analog to fsl_deck_D_set(), but uses the current time
       if (julianDay<0) and throws on error.
    */
    Deck & setCardD(double julianDay = -1.0);

    /**
       Analog to fsl_deck_E_set() but throws on error and returns this
       object on success. If the 2nd argument is less than 0 then
       the current time is used by default.
    */
    Deck & setCardE( fsl_uuid_cstr uuid, double julian = -1.0 );

    /**
       Analog to fsl_deck_F_add() but throws on error and returns this
       object on success.
    */
    Deck & addCardF(char const * name, fsl_uuid_cstr uuid,
                    fsl_fileperm_e perm = FSL_FILE_PERM_REGULAR, 
                    char const * oldName = NULL);

    /**
       Analog to fsl_deck_J_add() but throws on error and returns this
       object on success.
    */
    Deck & addCardJ( char isAppend, char const * key, char const * value );

    /**
       Analog to fsl_deck_K_set() but throws on error and returns this
       object on success.
    */
    Deck & setCardK(fsl_uuid_cstr uuid);

    /**
       Analog to fsl_deck_L_set() but throws on error and returns this
       object on success.
    */
    Deck & setCardL( char const * title );

    /**
       Analog to fsl_deck_M_add() but throws on error and returns this
       object on success.
    */
    Deck & addCardM(fsl_uuid_cstr uuid);

    /**
       Analog to fsl_deck_N_set() but throws on error and returns this
       object on success.
    */
    Deck & setCardN(char const * name);

    /**
       Analog to fsl_deck_P_add() but throws on error and returns this
       object on success.
    */
    Deck & addCardP(fsl_uuid_cstr uuid);

    /**
       Analog to fsl_deck_Q_add() but throws on error and returns this
       object on success.
    */
    Deck & addCardQ(char type, fsl_uuid_cstr target,
                    fsl_uuid_cstr baseline);

    /**
       Analog to fsl_deck_T_add() but throws on error and returns this
       object on success.
    */
    Deck & addCardT(fsl_tagtype_e tagType,
                    char const * name,
                    fsl_uuid_cstr uuid = NULL,
                    char const * value = NULL);

    /**
       Sets the U-card, analog to fsl_deck_U_set().  If name is NULL
       or !*name then fsl_cx_user_get(this->context()) is used
       to fetch the name. An empty name is not legal.

       Throws on error.
     */
    Deck & setCardU(char const * name = NULL);

    /**
       Analog to fsl_deck_W_set() but throws on error and returns this
       object on success.
    */
    Deck & setCardW(char const * content, fsl_int_t len = -1);

    /**
       If this is a CHECKIN deck and it is a delta manifest then its
       baseline is lazily loaded (if needed) and returned.  Throws on
       loading error. Returns NULL if this is not CHECKIN or not a
       delta manifest. The returned object is owned by this object
       and will be cleaned up when it is or when the B-card is re-set
       (setCardB()).
    */
    Deck * baseline();

    /**
       An iterator type for traversing lists of T-cards (tags) in a
       deck.

       Example usage:

       @code
       Deck::TCardIterator it(myDeck);
       Deck::TCardIterator end;
       for( ; it != end; ++it ){
         std::cout
           << fsl_tag_prefix_char(it->type)
           << it->name << '\n';
       }
       @endcode
    */
    class TCardIterator : public FslListIterator<fsl_card_T const *>{
    private:
      typedef FslListIterator<fsl_card_T const *> ParentType;
    public:
      /**
         Constructs a "begin" iterator for d's T-cards.
       */
      TCardIterator(Deck & d);
      /**
         Constructs an "end" iterator for a T-card list.
      */
      TCardIterator();
      ~TCardIterator() throw();

      /**
         Returns the same as *this, but throws for an end
         iterator.
      */
      fsl_card_T const * operator->() const;
    };

    /**
       An STL-style iterator class for use with traversing the F-cards
       in a Deck object. Because of how delta manifests work, F-cards
       have rather intricate traversal rules. This class helps hide
       those from the client (the only one it exposes is that F-cards
       are required to be in strict lexical order).

       Reminder: client code must be prepared to handle F-cards with
       NULL UUIDs. They appear when a file is removed between a
       baseline manifest and its delta. The delta marks deletions with
       a NULL UUID. A baseline manifest marks deletions by simply not
       including the file in the manifest (no F-card). The library
       "could" skip such entries when iterating, but knowing about
       deleted entries is useful at times.

       Potential TODO: a flag to this class which tells it to
       skip over deleted entries.
    */
    class FCardIterator {
    private:
      Deck * d;
      fsl_card_F const * fc;
      bool skipDeleted;
      void assertHasDeck();
    public:
      /**
         Rewinds d's F-card list and initializes this iterator to point
         to the first F-card in d. Remember that only decks of type
         FSL_SATYPE_CHECKIN have F-cards. Throws if rewinding fails (it
         only fails if lazy loading of a baseline manifest fails).
      */
      explicit FCardIterator(Deck & d, bool skipDeletedFiles = false);

      /**
         Constructs an "end" iterator.
      */
      FCardIterator() throw();

      ~FCardIterator() throw();

      /**
         Prefix increment: advances iterator and returns the new value.

         A no-op for an "end" iterator.

         If the skip-deleted flag was passed to the constructor then
         F-cards which represent deleted entries are skipped during
         traversal.
      */
      FCardIterator & operator++();

      /**
         The current F-card, or NULL at the end of the list.
      */
      fsl_card_F const * operator*();

      /**
         Convenience operator. Throws for an "end" iterator.
      */
      fsl_card_F const * operator->();

      /** Compares this object and rhs by name. */
      bool operator==(FCardIterator const &rhs) const throw();
      /** Compares this object and rhs by name. */
      bool operator!=(FCardIterator const &rhs) const throw();
      /** Compares this object and rhs by name. */
      bool operator<(FCardIterator const &rhs) const throw();
    };

  private:
    Context & cx;
    fsl_deck * d;
    Deck * deltaBase;
    bool ownsDeck;
    void setup(fsl_deck * d, fsl_satype_e type);
    void propagateError() const;
    void assertRC(char const * context, int rc) const;
    /** Not implemented - copying not currently allowed. */
    Deck(Deck const &);
    /** Not implemented - copying not currently allowed. */
    Deck & operator=(Deck const &);
  };

  /** Calls d.output(os) and returns os. */
  std::ostream & operator<<( std::ostream & os, Deck const & d );

  /**
     A fsl_appendf_f() implementation which requires that state be a
     std::ostream pointer. It uses std::ostream::write() to append n
     bytes of the data argument to the output stream. If the write()
     operation throws, this function catches it and returns FSL_RC_IO
     instead (because we propagating exceptions across the C API has
     undefined behaviour). Returns 0 on success, FSL_RC_IO if the
     stream is in an error state after the write.
  */
  fsl_int_t fsl_appendf_f_std_ostream( void * state, char const * data,
                                   fsl_int_t n );

  /**
     A fsl_output_f() implementation which requires that state be a
     std::ostream pointer. It uses std::ostream::write() to append n
     bytes of the data argument to the output stream. If the write()
     operation throws, this function catches it and returns FSL_RC_IO
     instead (because propagating exceptions across the C API has
     undefined behaviour). Returns 0 on success, FSL_RC_IO if the
     stream is in an error state after the write.
  */
  int fsl_output_f_std_ostream( void * state, void const * data,
                                fsl_size_t n );

  /**
     A fsl_input_f() implementation which requires state to be a
     std::istream pointer. Characters are read from the stream until
     *n bytes are read or EOF is reached. If EOF is reached, *n is set
     to the number of bytes consumed (and written to dest) before
     reaching EOF. If the input operation throws, this function
     catches it and returns FSL_RC_IO instead (because propagating
     exceptions across the C API has undefined behaviour)

     Example usage:

     @code
     char const * filename = "some-file";
     std::ifstream is(filename);
     if(!is.good()) throw Exception(FSL_RC_IO,"Cannot open: %s", filename); 
     fsl_buffer buf = fsl_buffer_empty;
     int rc = fsl_buffer_fill_from( &buf,
                                    fsl_input_f_std_istream,
                                    &is );
     if(rc) {...error...}
     else {...okay...}
     fsl_buffer_clear(&buf); // in error cases it might be partially filled!
     @endcode

     Better yet, use try/catch to better protect the buffer from
     leaks:

     @code
     fsl_buffer buf = fsl_buffer_empty;
     try{
       ... do i/o here ...
     }catch(...){
       fsl_buffer_clear(&buf);
       throw;
     }
     fsl_buffer_clear(&buf);
     @endcode

     _Even better_, use the Buffer class to manage buffer memory
     lifetime, making it inherently exception-safe:

     @code
     std::ifstream is(filename);
     Buffer buf;
     int rc = fsl_buffer_fill_from(buf, fsl_input_f_std_istream, &is );
     if(rc) throw Exception(rc); // now buf will not leak
     ...
     @endcode
  */
  int fsl_input_f_std_istream( void * state, void * dest, fsl_size_t * n );

  /**
     A std::streambuf impl which redirects a std::streambuf to
     fsl_output(). Can be used, e.g. to redirect std::cout and
     std::cerr to fsl_output().
  */
  class ContextOStreamBuf : public std::streambuf {
  private:
    fsl_cx * f;
    std::ostream * m_os;
    std::streambuf * m_old;
    void setup( fsl_cx * f );
  public:
    /**
       Redirects os's buffer to use this object, such that all output
       sent to os will instead go through this buffer to
       fsl_output(f,...). os must outlive this object. When this
       object destructs, os's old buffer is restored.

       Throws if f is NULL.

       Example:

       @code
       ContextOStreamBuf sb(myFossil, std::cout);
       std::cout << "This now goes through fsl_output(myFossil,...).\n";
       @endcode
    */
    ContextOStreamBuf( fsl_cx * f, std::ostream & os );

    /**
       Equivalent to passing cx.handle() to the other two-arg ctor.
    */
    ContextOStreamBuf( Context & cx, std::ostream & os );

    /**
       Redirects all output sent to this buffer to fsl_output(f,...).

       Throws if f is NULL.
    */
    explicit ContextOStreamBuf( fsl_cx * f );

    /**
       Equivalent to passing cx.handle() to the other one-arg ctor.
    */
    explicit ContextOStreamBuf( Context & cx );

    /**
       Flushes the buffer via this->sync();

       If this object wraps a stream, that streams buffer is then
       restored to its prior state.
    */
    virtual ~ContextOStreamBuf() throw();

    /**
       Outputs c as a single char via fsl_output(), using the fsl_cx
       instance passed to the constructor.

       On a write error it throws, else it returns 0.
    */
    virtual int overflow( int c );

    /**
       Falls fsl_flush(), passing it the fsl_cx instance passed to the
       ctor. Returns the result of that call.
    */
    virtual int sync();

  };

  /**
     A std::ostream which redirects its output to the output channel
     configured for a fsl_cx instance.

     Example usage:

     @code
     ContextOStream os(someContext.handle());
     os << "hi, world!\n"; // goes through fsl_output()
     @endcode
  */
  class ContextOStream : public std::ostringstream {
  private:
    fsl_cx * f;
    ContextOStreamBuf * sb;
  public:

    /**
       Sets up this buffer to direct all stream output sent to this
       buffer to fsl_output() instead, using f as the first argument
       to that function.

       Ownership of f is not changed. f must outlive this object.
     */
    explicit ContextOStream( fsl_cx * f );

    /**
       Equivalent to passing cx.handle() to the other ctor.
    */
    explicit ContextOStream( Context & cx );

    /**
       If initialized, it calls fsl_flush(), otherwise it has
       no visible side-effects.
    */
    virtual ~ContextOStream() throw();

    /**
       Appends a formatted string, as per fsl_outputf(), to the
       stream. This is primarily intended for adding SQL-related
       escaping to the buffer using the %q/%Q specifiers.

       Returns this object.
    */
    ContextOStream & appendf(char const * fmt, ...);
  };

  /**
     A std::streambuf impl which redirects a std::streambuf to a
     fsl_output_f(). It can be used, e.g. to redirect std::cout and
     std::cerr to a client-specific callback.
  */
  class FslOutputFStreamBuf : public std::streambuf {
  private:
    fsl_output_f out;
    void * outState;
    std::ostream * m_os;
    std::streambuf * m_old;
    void setup( fsl_output_f f, void * state );
  public:
    /**
       Redirects os's buffer to use this object, such that all output
       sent to os will instead go through this buffer to
       fsl_output(f,...). os must outlive this object. When this
       object destructs, os's old buffer is restored.

       Throws if !out.

       Example:

       @code
       FslOutputFStreamBuf sb(myCallback, callbackState, std::cout);
       std::cout << "This now goes through fsl_output(myFossil,...).\n";
       @endcode
    */
    FslOutputFStreamBuf( fsl_output_f out, void * outState,
                       std::ostream & os );

    /**
       Sets up output sent to this stream to go throug
       out(outState,...).

       Throws if !out.
     */
    FslOutputFStreamBuf( fsl_output_f out, void * outState );

    /**
       If this object wraps a stream, that stream's buffer is restored
       to its prior state.
    */
    virtual ~FslOutputFStreamBuf() throw();

    /**
       Outputs c as a single byte via the output function provided to
       the ctor. Throws on error.
    */
    virtual int overflow( int c );

    /**
       Does nothing. Returns 0.
    */
    virtual int sync();
  };


  /**
     This std::ostream subclass which proxies a fsl_output_f()
     implementation, sending all output to that function.

     The stream throws on output errors.

     Example usage, sending output to a Buffer using stream
     operators:

     @code
     Buffer buf;
     FslOutputFStream os(fsl_output_f_buffer, buf.handle());
     // ^^^ For this particular case MAKE SURE to pass the C
     // fsl_buffer handle, NOT the C++ Buffer handle!
     os << "hi, world!";
     assert(10==buf.used());

     // Or, more simply:
     BufferOStream bos(buf);
     bos << "hi, world!";
     @endcode
  */
  class FslOutputFStream : public std::ostream {
  private:
    /** The underlying proxy buffer. */
    FslOutputFStreamBuf * sb;
    /**
       fsl_appendf_f() impl which requires state to be a
       FslOutputFStream pointer. All output gets sent to this stream's
       proxy function.
    */
    static fsl_int_t fslAppendfF( void * state, char const * s, fsl_int_t n );

  public:

    /**
       Sets up this stream to direct all stream output sent to this
       buffer to out(outState, ...) instead.

       Ownership of outState is not changed. outState, if not NULL,
       must outlive this object.

       Throws if !out.
    */
    explicit FslOutputFStream( fsl_output_f out, void * outState );

    /**
       Cleans up its internal resources.
    */
    virtual ~FslOutputFStream() throw();

    /**
       Appends a formatted string, as per fsl_outputf(), to the
       stream. This is primarily intended for adding SQL-related
       escaping to the buffer using the %q/%Q specifiers.

       Returns this object.
    */
    FslOutputFStream & appendf(char const * fmt, ...);
  };


  class BufferOStream : public FslOutputFStream{
  public:
    /**
       Sets up this object to redirect all stream output to the given
       buffer. Ownership of b is not changed and b must outlive this
       stream. It is legal to implicitly convert a Buffer object for
       this purpose.

       Throws if b is NULL.
    */
    explicit BufferOStream(fsl_buffer * b);
    /**
       Does nothing.
    */
    ~BufferOStream() throw();
  };

}/*namespace fsl*/

#endif
/* ORG_FOSSIL_SCM_LIBFOSSIL_HPP_INCLUDED */
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted resources/c_lists-supermacro.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
/*
  This file is a "supermacro", intended to be included multiple times
  by related client code.

  Inputs:

  #define LIST_T list_type
  #define VALUE_T value_type
  #define VALUE_T_IS_PTR 1 // IF value_type is ptr-qualified
  #define LIST_DECLARE   // decls will be emitted.
  #define LIST_IMPLEMENT // impls will be emitted.

  VALUE_T_IS_PTR removes some routines, but is needed for handling
  (T**) lists propertly.

  list_type must be a struct with at least the following members:

  struct list_type {
  VALUE_T list; // or (VALUE_T*) if VALUE_T_IS_PTR
  cwal_size_t used; // number of used entries in the list
  cwal_size_t capacity; // number of items allocated in the list
  };

  Additional members are optional, and unused by this code.
  
  This file provides routines for (de)allocating, growing,
  visiting, and cleaning up list contents.

  The generated functions are named LIST_T_funcname(),
  e.g. list_type_reserve().
*/

#if !defined(VALUE_T_IS_PTR)
#  define VALUE_T_IS_PTR 0
#endif
#define SYM2(X,Y) X ## Y
#define SYM(X,Y) SYM2(X,Y)
#define STR2(T) # T
#define STR(T) STR2(T)

#if 0 && defined(LIST_IMPLEMENT)
#include <string.h> /* memset() */
#include <assert.h>
#endif

#ifdef LIST_DECLARE
/**
   Possibly reallocates self->list, changing its size. This function
   ensures that self->list has at least n entries. If n is 0 then
   the list is deallocated (but the self object is not), BUT THIS DOES NOT
   DO ANY TYPE-SPECIFIC CLEANUP of the items. If n is less than or equal
   to self->capacity then there are no side effects. If n is greater
   than self->capacity, self->list is reallocated and self->capacity
   is adjusted to be at least n (it might be bigger - this function may
   pre-allocate a larger value).

   Passing an n of 0 when self->capacity is 0 is a no-op.

   Newly-allocated items will be initialized with NUL bytes.
   
   Returns the total number of items allocated for self->list.  On
   success, the value will be equal to or greater than n (in the
   special case of n==0, 0 is returned). Thus a return value smaller
   than n is an error. Note that if n is 0 or self is NULL then 0 is
   returned.

   The return value should be used like this:

   @code
   cwal_size_t const n = number of bytes to allocate;
   if( n > my_list_t_reserve( myList, n ) ) { ... error ... }
   // Or the other way around:
   if( my_list_t_reserve( myList, n ) < n ) { ... error ... }
   @endcode
*/
cwal_size_t SYM(LIST_T,_reserve)( cwal_engine * e, LIST_T * self, cwal_size_t n );

/**
   Appends a bitwise copy of cp to self->list, expanding the list as
   necessary and adjusting self->used.

   Ownership of cp is unchanged by this call. cp may not be NULL.

   Returns 0 on success, CWAL_RC_MISUSE if any argument is NULL,
   or CWAL_RC_OOM on allocation error.
*/
int SYM(LIST_T,_append)( cwal_engine * e, LIST_T * self, VALUE_T cp );

/** @typedef typedef int (*LIST_TYPE_visitor_f)(void * p, void * visitorState )
   
   Generic visitor interface for cwal_list lists.  Used by
   LIST_TYPE_visit(). p is the pointer held by that list entry and
   visitorState is the 4th argument passed to cwal_list_visit().

   Implementations must return 0 on success. Any other value causes
   looping to stop and that value to be returned, but interpration of
   the value is up to the caller (it might or might not be an error,
   depending on the context). Note that client code may use custom
   values, and is not restricted to cwal_rc values.
*/

#if VALUE_T_IS_PTR
typedef int (*SYM(LIST_T,_visitor_f))(VALUE_T obj, void * visitorState );
#else
typedef int (*SYM(LIST_T,_visitor_f))(VALUE_T * obj, void * visitorState );
#endif

/**
   For each item in self->list, visitor(item,visitorState) is called.
   The item is owned by self. The visitor function MUST NOT free the
   item, but may manipulate its contents if application rules do not
   specify otherwise.

   If order is 0 or greater then the list is traversed from start to
   finish, else it is traverse from end to begin.

   Returns 0 on success, non-0 on error.

   If visitor() returns non-0 then looping stops and that code is
   returned.
*/
int SYM(LIST_T,_visit)( LIST_T * self, char order,
                        SYM(LIST_T,_visitor_f) visitor, void * visitorState );
#if VALUE_T_IS_PTR
/**
   Works similarly to the visit operation without the _p suffix except
   that the pointer the visitor function gets is a (**) pointing back
   to the entry within this list. That means that callers can assign
   the entry in the list to another value during the traversal process
   (e.g. set it to 0). If shiftIfNulled is true then if the callback
   sets the list's value to 0 then it is removed from the list and
   self->used is adjusted (self->capacity is not changed).
*/
int SYM(LIST_T,_visit_p)( LIST_T * self, char order, char shiftIfNulled,
                          SYM(LIST_T,_visitor_f) visitor, void * visitorState );
#endif

#endif
/* LIST_DECLARE */


#ifdef LIST_IMPLEMENT

cwal_size_t SYM(LIST_T,_reserve)( cwal_engine * e, LIST_T * self, cwal_size_t n )
{
    if( !self ) return 0;
    else if(0 == n)
    {
        if(0 == self->capacity) return 0;
        cwal_free(e, self->list);
        self->list = NULL;
        self->capacity = self->used = 0;
        return 0;
    }
    else if( self->capacity >= n )
    {
        return self->capacity;
    }
    else
    {
        size_t const sz = sizeof(VALUE_T) * n;
        VALUE_T * m = (VALUE_T*)cwal_realloc( e, self->list, sz );
        if( ! m ) return self->capacity;
        /* Zero out the new elements... */
        memset( m + self->capacity, 0, (sizeof(VALUE_T)*(n-self->capacity)));
        self->capacity = n;
        self->list = m;
        return n;
    }
}

int SYM(LIST_T,_append)( cwal_engine * e, LIST_T * self, VALUE_T cp )
{
    if( !e || !self || !cp ) return CWAL_RC_MISUSE;
    else if( self->capacity > SYM(LIST_T,_reserve)(e, self, self->used+1) )
    {
        return CWAL_RC_OOM;
    }
    else
    {
        self->list[self->used++] = cp;
#if VALUE_T_IS_PTR
        if(self->used< self->capacity-1) self->list[self->used]=0;
#endif
        return 0;
    }
}

int SYM(LIST_T,_visit)( LIST_T * self, char order,
                        SYM(LIST_T,_visitor_f) visitor, void * visitorState )
{
    int rc = CWAL_RC_OK;
    if( self && self->used && visitor )
    {
        int i = 0;
        int pos = (order<0) ? self->used-1 : 0;
        int step = (order<0) ? -1 : 1;
        for( rc = 0; (i < self->used) && (0 == rc); ++i, pos+=step )
        {
#if VALUE_T_IS_PTR
            VALUE_T obj = self->list[pos];
#else
            VALUE_T * obj = &self->list[pos];
#endif
            if(obj) rc = visitor( obj, visitorState );
#if VALUE_T_IS_PTR
            /* cwal-specific hack b/c obj can be removed during traversal... */
            if( obj != self->list[pos] ){
                --i;
                if(order>=0) pos -= step;
            }
#endif

        }
    }
    return rc;
}

#if VALUE_T_IS_PTR
int SYM(LIST_T,_visit_p)( LIST_T * self, char order,
                          char shiftIfNulled,
                          SYM(LIST_T,_visitor_f) visitor, void * visitorState )
{
    int rc = CWAL_RC_OK;
    if( self && self->used && visitor )
    {
        int i = 0;
        int pos = (order<0) ? self->used-1 : 0;
        int step = (order<0) ? -1 : 1;
        for( rc = 0; (i < (int)self->used) && (0 == rc); ++i, pos+=step )
        {
            VALUE_T obj = self->list[pos];
            if(obj) {
                assert((order<0) && "TEST THAT THIS WORKS WITH IN-ORDER!");
                rc = visitor( &self->list[pos], visitorState );
                if( shiftIfNulled && !self->list[pos]){
                    int x = pos;
                    int const to = self->used-pos;
                    /*MARKER("i=%d pos=%d x=%d to=%d\n",i, pos, x, to);*/
                    assert( to < (int) self->capacity );
                    for( ; x < to; ++x ) self->list[x] = self->list[x+1];
                    if( x < (int)self->capacity ) self->list[x] = 0;
                    --i;
                    --self->used;
                    if(order>=0) pos -= step;
                }
            }
        }
    }
    return rc;
}
#endif


#if !VALUE_T_IS_PTR
/**
   Reduces self->used by 1. Returns 0 on success.
   Errors include:

   (!self) or (0 == self->used)
*/
int SYM(LIST_T,_pop_back)( cwal_engine * e, LIST_T * self, void (*cleaner)(VALUE_T * obj) )
{
    if( !self ) return CWAL_RC_MISUSE;
    else if( ! self->used ) return cson_rc.RangeError;
    else
    {
        cwal_size_t const ndx = --self->used;
        VALUE_T * val = &self->list[ndx];
        if(val && cleaner) cleaner(val);
        return 0;
    }
}
#endif

#endif
/* LIST_IMPLEMENT */

#undef LIST_IMPLEMENT
#undef LIST_DECLARE
#undef SYM
#undef SYM2
#undef LIST_T
#undef VALUE_T
#undef VALUE_T_IS_PTR
#undef STR
#undef STR2
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






























































































































































































































































































































































































































































































































































































Deleted resources/fossil-logo-3.png.

cannot compute difference between binary files

Deleted sql/checkout.sql.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
-- The VVAR table holds miscellanous information about the local database
-- in the form of name-value pairs.  This is similar to the VAR table
-- table in the repository except that this table holds information that
-- is specific to the local checkout.
--
-- Important Variables:
--
--     repository        Full pathname of the repository database
--     user-id           Userid to use
--
CREATE TABLE ckout.vvar(
  name TEXT PRIMARY KEY NOT NULL,  -- Primary name of the entry
  value CLOB,                      -- Content of the named parameter
  CHECK( typeof(name)='text' AND length(name)>=1 )
);

-- Each entry in the vfile table represents a single file in the
-- current checkout.
--
-- The file.rid field is 0 for files or folders that have been
-- added but not yet committed.
--
-- Vfile.chnged is 0 for unmodified files, 1 for files that have
-- been edited or which have been subjected to a 3-way merge.
-- Vfile.chnged is 2 if the file has been replaced from a different
-- version by the merge and 3 if the file has been added by a merge.
-- Vfile.chnged is 4|5 is the same as 2|3, but the operation has been
-- done by an --integrate merge.  The difference between vfile.chnged==2|4
-- and a regular add is that with vfile.chnged==2|4 we know that the
-- current version of the file is already in the repository.
--
CREATE TABLE ckout.vfile(
  id INTEGER PRIMARY KEY,           -- ID of the checked out file
  vid INTEGER REFERENCES blob,      -- The baseline this file is part of.
  chnged INT DEFAULT 0,             -- 0:unchnged 1:edited 2:m-chng 3:m-add 4:i-chng 5:i-add
  deleted BOOLEAN DEFAULT 0,        -- True if deleted
  isexe BOOLEAN,                    -- True if file should be executable
  islink BOOLEAN,                   -- True if file should be symlink
  rid INTEGER,                      -- Originally from this repository record
  mrid INTEGER,                     -- Based on this record due to a merge
  mtime INTEGER,                    -- Mtime of file on disk. sec since 1970
  pathname TEXT,                    -- Full pathname relative to root
  origname TEXT,                    -- Original pathname. NULL if unchanged
  mhash TEXT,                       -- Hash of mrid iff mrid!=rid. Added 2019-01-19.
  UNIQUE(pathname,vid)
);

-- This table holds a record of uncommitted merges in the local
-- file tree.  If a VFILE entry with id has merged with another
-- record, there is an entry in this table with (id,merge) where
-- merge is the RECORD table entry that the file merged against.
-- An id of 0 or <-3 here means the version record itself.  When
-- id==(-1) that is a cherrypick merge, id==(-2) that is a
-- backout merge and id==(-4) is a integrate merge.

CREATE TABLE ckout.vmerge(
  id INTEGER REFERENCES vfile,      -- VFILE entry that has been merged
  merge INTEGER,                    -- Merged with this record
  mhash TEXT                        -- SHA1/SHA3 hash for merge object
);
CREATE UNIQUE INDEX ckout.vmergex1 ON vmerge(id,mhash);

-- The following trigger will prevent older versions of Fossil that
-- do not know about the new vmerge.mhash column from updating the
-- vmerge table.  This must be done with a trigger, since legacy Fossil
-- uses INSERT OR IGNORE to update vmerge, and the OR IGNORE will cause
-- a NOT NULL constraint to be silently ignored.
CREATE TRIGGER ckout.vmerge_ck1 AFTER INSERT ON vmerge
WHEN new.mhash IS NULL BEGIN
  SELECT raise(FAIL,
  'trying to update a newer checkout with an older version of Fossil');
END;

-- Identifier for this file type.
-- The integer is the same as 'FSLC'.
PRAGMA ckout.application_id=252006674;
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
























































































































































Deleted sql/config.sql.

1
2
3
4
5
6
7
8
9
10
11
-- This file contains the schema for the database that is kept in the
-- ~/.fossil file and that stores information about the users setup.
--
CREATE TABLE cfg.global_config(
  name TEXT PRIMARY KEY,
  value TEXT
);

-- Identifier for this file type.
-- The integer is the same as 'FSLG'.
PRAGMA cfg.application_id=252006675;
<
<
<
<
<
<
<
<
<
<
<






















Deleted sql/forum.sql.

1
2
3
4
5
6
7
8
CREATE TABLE repo.forumpost(
  fpid INTEGER PRIMARY KEY,  -- BLOB.rid for the artifact
  froot INT,                 -- fpid of the thread root
  fprev INT,                 -- Previous version of this same post
  firt INT,                  -- This post is in-reply-to
  fmtime REAL                -- When posted.  Julian day
);
CREATE INDEX repo.forumthread ON forumpost(froot,fmtime);
<
<
<
<
<
<
<
<
















Deleted sql/q-ancestry.sql.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
-- Here's a reference point for a branch/merge test:
-- http://fossil.wanderinghorse.net/repos/libfossil/index.cgi/timeline?n=20&y=ci&b=2014-01-29+18:22:00

-- All ancestors (direct or merged!) of the checkin
-- RID given in the first SELECT...
WITH RECURSIVE
-- Top-level configuration for this query...
  conf(id,primaryOnly,cutoffDays,historyLimit) AS (
    SELECT
-- origin RID or UUID. Change this to the RID/UUID of the
-- origin for ancestry tracking:
--    SELECT 3003 /*2984*/ /*3285*/ as id -- origin RID
--    SELECT '8f89acc0f05df7bae1e7946efe5324f1e6905a9e' as id

-- d508 is one step after a merge:
     'd508a2e7ab04caf0f228e71babb1c9c69894a903'
--    3089
-- d508 poses an interesting problem: the branch point
-- (6b581c8) is listed twice, once as the parent along
-- trunk and aonce along the branch. To make that data
-- useful we have to add a "childRid" field to the result
-- so that its lineage is no longer ambiguous.

-- 1d59 is at the end of that merged branch.
--    SELECT '1d59e4291a04f17bbdbe74154bb4d53c095a284a' as id

      AS id,
      0 AS primaryOnly, -- true to follow only primary (non-branch) parents
      0.0 AS cutoffDays, -- max number of days back from origin version
      10 AS historyLimit -- max number of entries from origin version
  ),
  origin(rid, mtime, cutoffTime, branch) AS(
    -- origin RID
    SELECT b.rid as rid,
           e.mtime as mtime,
--           (e.mtime - 10) as cutoffTime -- Julian days
           (CASE WHEN 0.0=conf.cutoffDays THEN 0.0 ELSE mtime - conf.cutoffDays END) cutoffTime,
           (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref
               WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
               AND tagxref.rid=b.rid AND tagxref.tagtype>0) as branch
    FROM blob b, event e, conf
    WHERE
--      b.rid=conf.id
-- or use the UUID of the origin:
      -- b.uuid=conf.id
      (CASE WHEN 'integer'=typeof(conf.id) THEN b.rid=conf.id ELSE b.uuid=conf.id END)
    AND e.objid=b.rid
  ),
  lineage(rid,childRid,uuid,tm,user,branch,comment) AS (
     SELECT origin.rid, 0, b.uuid, origin.mtime, e.user,
            origin.branch,
            coalesce(e.ecomment,e.comment)
        FROM blob b, event e, origin
        WHERE b.rid=origin.rid and e.objid=b.rid
     UNION ALL
     SELECT p.pid, p.cid, b.uuid, e.mtime, e.user,
           (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref
               WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
               AND tagxref.rid=b.rid AND tagxref.tagtype>0) as branch,
            coalesce(e.ecomment,e.comment)
     FROM plink p, blob b,
          lineage a, event e,
          origin, conf
        WHERE
        p.pid=b.rid
        AND p.cid=a.rid
        AND e.objid=p.pid
-- Whether or not to follow non-merge parents...
        AND (CASE WHEN conf.primaryOnly THEN p.isprim ELSE 1 END)
-- Only trace back this far in time...
        AND e.mtime >= origin.cutoffTime
-- ^^^ if that is removed, also remove origin from the join!

-- Optionally limit it to the first N
-- lineage (including the original checkin):
    LIMIT (SELECT historyLimit FROM conf)
 )
SELECT
       a.rid rid,
       a.childRid,
       substr(a.uuid,0,8) uuid,
       datetime(a.tm,'localtime') time,
       a.branch,
       substr(a.comment,0,20)||'...' comment
from lineage a, lineage b
-- WHERE b.rid=a.childRid
-- OR a.rid=b.rid
-- OR (a.childRid=0 AND b.rid=a.rid)
WHERE a.rid=b.childRid
-- OR (b.childRid=0 AND a.rid=b.rid)
ORDER BY time DESC
;
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
























































































































































































Deleted sql/q-filename-history.sql.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
-- For a given file name, find all changes in the history of
-- that file, stopping at the point where it was added or
-- renamed.
SELECT
substr(b.uuid,0,12) as manifestUuid,
datetime(p.mtime) as manifestTime,
--       ml.*,
ml.mid AS manifestRid,
-- b.size AS manifestSize,
ml.pid AS parentManifestRid,
ml.fid AS fileContentRid

-- fileContentRid=0 at the point of a rename (under the old name). The
-- fields (manifest*, prevFileContentRid) will match at the rename
-- point across this query and the same query against the renamed
-- file. Only (fileContentRid, filename) always differ across the
-- rename-point records.
-- renamedEntry.fileContentRid=origEntry.prevFileContentRid if no
-- changes were made to the file between renaming and committing.
,
ml.pid AS prevFileContentRid
  -- prevFileContentRid=0 at start of history,
  -- prevFileContentRid=fileContentRid for a file which was just
  -- renamed UNLESS it was modified after the rename, in which case...
  -- ???
,
fn.name AS filename,
prevfn.name AS priorname
FROM
mlink ml, -- map of files/filenames to checkins
filename fn,
blob b, -- checkin manifest
plink p -- checkin heritage
-- This LEFT JOIN adds rename info:
LEFT JOIN filename prevfn ON ml.pfnid=prevfn.fnid
WHERE
(fn.name
    -- IN('f-status.c', 'f-apps/f-status.c')
    -- GLOB '*f-status.c'
    -- = 'test.c'
    = 'f-status.c' -- was renamed/moved to...
    OR fn.name = 'f-apps/f-status.c' -- was target of ^^^^ that rename
    -- = 'src/fsl_io.c' -- short history
    -- = 'Makefile.in' -- long history with merge
)
-- Interesting: with this is will ONLY report the rename point:
--   AND ml.pfnid=prevfn.fnid
AND ml.fnid=fn.fnid
AND ml.mid=b.rid
AND p.cid=ml.mid -- renamed file will have non-0 here
ORDER BY manifestTime DESC
;
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<








































































































Deleted sql/q-mandelbrot.sql.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
WITH RECURSIVE
  xaxis(x) AS (VALUES(-2.0) UNION ALL SELECT x+0.05 FROM xaxis WHERE x<1.2),
  yaxis(y) AS (VALUES(-1.0) UNION ALL SELECT y+0.1 FROM yaxis WHERE y<1.0),
  m(iter, cx, cy, x, y) AS (
    SELECT 0, x, y, 0.0, 0.0 FROM xaxis, yaxis
    UNION ALL
    SELECT iter+1, cx, cy, x*x-y*y + cx, 2.0*x*y + cy FROM m 
     WHERE (x*x + y*y) < 4.0 AND iter<28
  ),
  m2(iter, cx, cy) AS (
    SELECT max(iter), cx, cy FROM m GROUP BY cx, cy
  ),
  a(t) AS (
    SELECT group_concat( substr(' .+*#', 1+min(iter/7,4), 1), '') 
    FROM m2 GROUP BY cy
  )
SELECT group_concat(rtrim(t),x'0a') FROM a;

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




































Deleted sql/q-sqlite-compile-opt.sql.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- Posted by Petite Abeille to the sqlite-users mailing list:
-- fetch all compile options used for this version of sqlite3:
with
Option( name, position )
as
(
  select  sqlite_compileoption_get( 1 ) as name,
          0 as position

  union all
  select  sqlite_compileoption_get( position + 1 ) as name,
          position + 1 as position
  from    Option
  where   sqlite_compileoption_get( position + 1 ) is not null
)
select    DISTINCT name
from      Option
order by  name
;
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






































Deleted sql/q-vfile-status.sql.

1
2
3
4
5
6
7
8
9
10
11
12
13
-- provides output similar to fossil(1)'s status command, with the caveat that
-- that that command is the one which updates the vtile table referenced here.
--
-- Requires both a checkout and repo db to be attached.
SELECT
        id,vid, mrid, deleted,
        chnged,
        datetime(mtime,'unixepoch','localtime') as local_time,
        size, uuid, origname, pathname
FROM vfile LEFT JOIN blob ON vfile.mrid=blob.rid
WHERE vid=(SELECT value FROM vvar WHERE name='checkout')
AND chnged
ORDER BY pathname;
<
<
<
<
<
<
<
<
<
<
<
<
<


























Deleted sql/q-wiki-lineage.sql.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
WITH RECURSIVE
 page_name(name) AS(
--   select substr(t.tagname,6) from tag t where t.tagname glob 'wiki-*'
--   SELECT 'home' -- long history
--   UNION ALL
   SELECT 'HackersGuide' -- short history
   UNION ALL
   SELECT 'building' -- moderate history
 ),
 wiki_tagids(name, rid,mtime) AS (
   SELECT page_name.name, x.rid AS rid, x.mtime AS mtime
   FROM tag t, tagxref x, page_name
   WHERE x.tagid=t.tagid
   AND t.tagname='wiki-'||page_name.name
--   ORDER BY mtime DESC
 ),
 wiki_lineage(name, rid,uuid, mtime, size, user) AS(
   SELECT wt.name name, wt.rid rid,
          b.uuid uuid,
          wt.mtime mtime,
          b.size size,
          e.user user
    FROM wiki_tagids wt,
         blob b,
         event e
    WHERE wt.rid=b.rid
    AND e.objid=b.rid
 )
SELECT name, rid,uuid,datetime(mtime,'localtime'),size,user
FROM wiki_lineage
ORDER BY mtime DESC;
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






























































Deleted sql/repo-static.sql.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
-- This file contains parts of the schema that are fixed and
-- unchanging across Fossil versions.


-- The BLOB and DELTA tables contain all records held in the repository.
--
-- The BLOB.CONTENT column is always compressed using zlib.  This
-- column might hold the full text of the record or it might hold
-- a delta that is able to reconstruct the record from some other
-- record.  If BLOB.CONTENT holds a delta, then a DELTA table entry
-- will exist for the record and that entry will point to another
-- entry that holds the source of the delta.  Deltas can be chained.
--
-- The blob and delta tables collectively hold the "global state" of
-- a Fossil repository.  
--
CREATE TABLE repo.blob(
  rid INTEGER PRIMARY KEY,        -- Record ID
  rcvid INTEGER,                  -- Origin of this record
  size INTEGER,                   -- Size of content. -1 for a phantom.
  uuid TEXT UNIQUE NOT NULL,      -- SHA1 hash of the content
  content BLOB,                   -- Compressed content of this record
  CHECK( length(uuid)>=40 AND rid>0 )
);
CREATE TABLE repo.delta(
  rid INTEGER PRIMARY KEY,                 -- Record ID
  srcid INTEGER NOT NULL REFERENCES blob   -- Record holding source document
);
CREATE INDEX repo.delta_i1 ON delta(srcid);

-------------------------------------------------------------------------
-- The BLOB and DELTA tables above hold the "global state" of a Fossil
-- project; the stuff that is normally exchanged during "sync".  The
-- "local state" of a repository is contained in the remaining tables of
-- the zRepositorySchema1 string.  
-------------------------------------------------------------------------

-- Whenever new blobs are received into the repository, an entry
-- in this table records the source of the blob.
--
CREATE TABLE repo.rcvfrom(
  rcvid INTEGER PRIMARY KEY,      -- Received-From ID
  uid INTEGER REFERENCES user,    -- User login
  mtime DATETIME,                 -- Time of receipt.  Julian day.
  nonce TEXT UNIQUE,              -- Nonce used for login
  ipaddr TEXT                     -- Remote IP address.  NULL for direct.
);
INSERT INTO repo.rcvfrom(rcvid,uid,mtime,nonce,ipaddr)
VALUES (1, 1, julianday('now'), NULL, NULL);

-- Information about users
--
-- The user.pw field can be either cleartext of the password, or
-- a SHA1 hash of the password.  If the user.pw field is exactly 40
-- characters long we assume it is a SHA1 hash.  Otherwise, it is
-- cleartext.  The sha1_shared_secret() routine computes the password
-- hash based on the project-code, the user login, and the cleartext
-- password.
--
CREATE TABLE repo.user(
  uid INTEGER PRIMARY KEY,        -- User ID
  login TEXT UNIQUE,              -- login name of the user
  pw TEXT,                        -- password
  cap TEXT,                       -- Capabilities of this user
  cookie TEXT,                    -- WWW login cookie
  ipaddr TEXT,                    -- IP address for which cookie is valid
  cexpire DATETIME,               -- Time when cookie expires
  info TEXT,                      -- contact information
  mtime DATE,                     -- last change.  seconds since 1970
  photo BLOB                      -- JPEG image of this user
);

-- The VAR table holds miscellanous information about the repository.
-- in the form of name-value pairs.
--
CREATE TABLE repo.config(
  name TEXT PRIMARY KEY NOT NULL,  -- Primary name of the entry
  value CLOB,                      -- Content of the named parameter
  mtime DATE,                      -- last modified.  seconds since 1970
  CHECK( typeof(name)='text' AND length(name)>=1 )
);

-- Artifacts that should not be processed are identified in the
-- "shun" table.  Artifacts that are control-file forgeries or
-- spam or artifacts whose contents violate administrative policy
-- can be shunned in order to prevent them from contaminating
-- the repository.
--
-- Shunned artifacts do not exist in the blob table.  Hence they
-- have not artifact ID (rid) and we thus must store their full
-- UUID.
--
CREATE TABLE repo.shun(
  uuid UNIQUE,          -- UUID of artifact to be shunned. Canonical form
  mtime DATE,           -- When added.  seconds since 1970
  scom TEXT             -- Optional text explaining why the shun occurred
);

-- Artifacts that should not be pushed are stored in the "private"
-- table.  Private artifacts are omitted from the "unclustered" and
-- "unsent" tables.
--
CREATE TABLE repo.private(rid INTEGER PRIMARY KEY);

-- An entry in this table describes a database query that generates a
-- table of tickets.
--
CREATE TABLE repo.reportfmt(
   rn INTEGER PRIMARY KEY,  -- Report number
   owner TEXT,              -- Owner of this report format (not used)
   title TEXT UNIQUE,       -- Title of this report
   mtime DATE,              -- Last modified.  seconds since 1970
   cols TEXT,               -- A color-key specification
   sqlcode TEXT             -- An SQL SELECT statement for this report
);

-- Some ticket content (such as the originators email address or contact
-- information) needs to be obscured to protect privacy.  This is achieved
-- by storing an SHA1 hash of the content.  For display, the hash is
-- mapped back into the original text using this table.  
--
-- This table contains sensitive information and should not be shared
-- with unauthorized users.
--
CREATE TABLE repo.concealed(
  hash TEXT PRIMARY KEY,    -- The SHA1 hash of content
  mtime DATE,               -- Time created.  Seconds since 1970
  content TEXT              -- Content intended to be concealed
);

-- The application ID helps the unix "file" command to identify the
-- database as a fossil repository.
PRAGMA repo.application_id=252006673;
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










































































































































































































































































Deleted sql/repo-transient.sql.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
-- This file contains parts of the schema that can change from one
-- version to the next. The data stored in these tables is
-- reconstructed from the information in the main repo schema by the
-- "rebuild" operation.

-- Filenames
--
CREATE TABLE repo.filename(
  fnid INTEGER PRIMARY KEY,    -- Filename ID
  name TEXT UNIQUE             -- Name of file page
);

-- Linkages between check-ins, files created by each check-in, and
-- the names of those files.
--
-- Each entry represents a file that changed content from pid to fid
-- due to the check-in that goes from pmid to mid.  fnid is the name
-- of the file in the mid check-in.  If the file was renamed as part
-- of the mid check-in, then pfnid is the previous filename.
--
-- There can be multiple entries for (mid,fid) if the mid check-in was
-- a merge.  Entries with isaux==0 are from the primary parent.  Merge
-- parents have isaux set to true.
--
-- Field name mnemonics:
--    mid = Manifest ID.  (Each check-in is stored as a "Manifest")
--    fid = File ID.
--    pmid = Parent Manifest ID.
--    pid = Parent file ID.
--    fnid = File Name ID.
--    pfnid = Parent File Name ID.
--    isaux = pmid IS AUXiliary parent, not primary parent
--
-- pid==0    if the file is added by check-in mid.
-- pid==(-1) if the file exists in a merge parents but not in the primary
--           parent.  In other words, if the file file was added by merge.
--           (TODO: confirm if/where this is used in fossil and then make sure
--           libfossil does so, too.)
-- fid==0    if the file is removed by check-in mid.
--
CREATE TABLE repo.mlink(
  mid INTEGER,        -- Check-in that contains fid
  fid INTEGER,        -- New file content RID. 0 if deleted
  pmid INTEGER,       -- Check-in RID that contains pid
  pid INTEGER,        -- Prev file content RID. 0 if new. -1 if from a merge
  fnid INTEGER REFERENCES filename,   -- Name of the file
  pfnid INTEGER,      -- Previous name. 0 if unchanged
  mperm INTEGER,                      -- File permissions.  1==exec
  isaux BOOLEAN DEFAULT 0             -- TRUE if pmid is the primary
);
CREATE INDEX repo.mlink_i1 ON mlink(mid);
CREATE INDEX repo.mlink_i2 ON mlink(fnid);
CREATE INDEX repo.mlink_i3 ON mlink(fid);
CREATE INDEX repo.mlink_i4 ON mlink(pid);

-- Parent/child linkages between checkins
--
CREATE TABLE repo.plink(
  pid INTEGER REFERENCES blob,    -- Parent manifest
  cid INTEGER REFERENCES blob,    -- Child manifest
  isprim BOOLEAN,                 -- pid is the primary parent of cid
  mtime DATETIME,                 -- the date/time stamp on cid.  Julian day.
  baseid INTEGER REFERENCES blob, -- Baseline if cid is a delta manifest.
  UNIQUE(pid, cid)
);
CREATE INDEX repo.plink_i2 ON plink(cid,pid);

-- A "leaf" checkin is a checkin that has no children in the same
-- branch.  The set of all leaves is easily computed with a join,
-- between the plink and tagxref tables, but it is a slower join for
-- very large repositories (repositories with 100,000 or more checkins)
-- and so it makes sense to precompute the set of leaves.  There is
-- one entry in the following table for each leaf.
--
CREATE TABLE repo.leaf(rid INTEGER PRIMARY KEY);

-- Events used to generate a timeline
--
CREATE TABLE repo.event(
  type TEXT,                      -- Type of event: 'ci', 'w', 'e', 't', 'g'
  mtime DATETIME,                 -- Time of occurrence. Julian day.
  objid INTEGER PRIMARY KEY,      -- Associated record ID
  tagid INTEGER,                  -- Associated ticket or wiki name tag
  uid INTEGER REFERENCES user,    -- User who caused the event
  bgcolor TEXT,                   -- Color set by 'bgcolor' property
  euser TEXT,                     -- User set by 'user' property
  user TEXT,                      -- Name of the user
  ecomment TEXT,                  -- Comment set by 'comment' property
  comment TEXT,                   -- Comment describing the event
  brief TEXT,                     -- Short comment when tagid already seen
  omtime DATETIME                 -- Original unchanged date+time, or NULL
);
CREATE INDEX repo.event_i1 ON event(mtime);

-- A record of phantoms.  A phantom is a record for which we know the
-- UUID but we do not (yet) know the file content.
--
CREATE TABLE repo.phantom(
  rid INTEGER PRIMARY KEY         -- Record ID of the phantom
);

-- A record of orphaned delta-manifests.  An orphan is a delta-manifest
-- for which we have content, but its baseline-manifest is a phantom.
-- We have to track all orphan manifests so that when the baseline arrives,
-- we know to process the orphaned deltas.
CREATE TABLE repo.orphan(
  rid INTEGER PRIMARY KEY,        -- Delta manifest with a phantom baseline
  baseline INTEGER                -- Phantom baseline of this orphan
);
CREATE INDEX repo.orphan_baseline ON orphan(baseline);

-- Unclustered records.  An unclustered record is a record (including
-- a cluster records themselves) that is not mentioned by some other
-- cluster.
--
-- Phantoms are usually included in the unclustered table.  A new cluster
-- will never be created that contains a phantom.  But another repository
-- might send us a cluster that contains entries that are phantoms to
-- us.
--
CREATE TABLE repo.unclustered(
  rid INTEGER PRIMARY KEY         -- Record ID of the unclustered file
);

-- Records which have never been pushed to another server.  This is
-- used to reduce push operations to a single HTTP request in the
-- common case when one repository only talks to a single server.
--
CREATE TABLE repo.unsent(
  rid INTEGER PRIMARY KEY         -- Record ID of the phantom
);

-- Each baseline or manifest can have one or more tags.  A tag
-- is defined by a row in the next table.
-- 
-- Wiki pages are tagged with "wiki-NAME" where NAME is the name of
-- the wiki page.  Tickets changes are tagged with "ticket-UUID" where 
-- UUID is the indentifier of the ticket.  Tags used to assign symbolic
-- names to baselines are branches are of the form "sym-NAME" where
-- NAME is the symbolic name.
--
CREATE TABLE repo.tag(
  tagid INTEGER PRIMARY KEY,       -- Numeric tag ID
  tagname TEXT UNIQUE              -- Tag name.
);
INSERT INTO repo.tag VALUES(1, 'bgcolor');         -- FSL_TAGID_BGCOLOR
INSERT INTO repo.tag VALUES(2, 'comment');         -- FSL_TAGID_COMMENT
INSERT INTO repo.tag VALUES(3, 'user');            -- FSL_TAGID_USER
INSERT INTO repo.tag VALUES(4, 'date');            -- FSL_TAGID_DATE
INSERT INTO repo.tag VALUES(5, 'hidden');          -- FSL_TAGID_HIDDEN
INSERT INTO repo.tag VALUES(6, 'private');         -- FSL_TAGID_PRIVATE
INSERT INTO repo.tag VALUES(7, 'cluster');         -- FSL_TAGID_CLUSTER
INSERT INTO repo.tag VALUES(8, 'branch');          -- FSL_TAGID_BRANCH
INSERT INTO repo.tag VALUES(9, 'closed');          -- FSL_TAGID_CLOSED
INSERT INTO repo.tag VALUES(10,'parent');          -- FSL_TAGID_PARENT
INSERT INTO repo.tag VALUES(11,'note');            -- FSL_TAG_NOTE
-- arguable, to force auto-increment to start at 100:
-- INSERT INTO tag VALUES(99,'FSL_TAGID_MAX_INTERNAL');

-- Assignments of tags to baselines.  Note that we allow tags to
-- have values assigned to them.  So we are not really dealing with
-- tags here.  These are really properties.  But we are going to
-- keep calling them tags because in many cases the value is ignored.
--
CREATE TABLE repo.tagxref(
  tagid INTEGER REFERENCES tag,   -- The tag that was added or removed
  tagtype INTEGER,                -- 0:-,cancel  1:+,single  2:*,propagate
  srcid INTEGER REFERENCES blob,  -- Artifact of tag. 0 for propagated tags
  origid INTEGER REFERENCES blob, -- check-in holding propagated tag
  value TEXT,                     -- Value of the tag.  Might be NULL.
  mtime TIMESTAMP,                -- Time of addition or removal. Julian day
  rid INTEGER REFERENCE blob,     -- Artifact tag is applied to
  UNIQUE(rid, tagid)
);
CREATE INDEX repo.tagxref_i1 ON tagxref(tagid, mtime);

-- When a hyperlink occurs from one artifact to another (for example
-- when a check-in comment refers to a ticket) an entry is made in
-- the following table for that hyperlink.  This table is used to
-- facilitate the display of "back links".
--
CREATE TABLE repo.backlink(
  target TEXT,           -- Where the hyperlink points to
  srctype INT,           -- 0: check-in  1: ticket  2: wiki
  srcid INT,             -- rid for checkin or wiki.  tkt_id for ticket.
  mtime TIMESTAMP,       -- time that the hyperlink was added. Julian day.
  UNIQUE(target, srctype, srcid)
);
CREATE INDEX repo.backlink_src ON backlink(srcid, srctype);

-- Each attachment is an entry in the following table.  Only
-- the most recent attachment (identified by the D card) is saved.
--
CREATE TABLE repo.attachment(
  attachid INTEGER PRIMARY KEY,   -- Local id for this attachment
  isLatest BOOLEAN DEFAULT 0,     -- True if this is the one to use
  mtime TIMESTAMP,                -- Last changed.  Julian day.
  src TEXT,                       -- UUID of the attachment.  NULL to delete
  target TEXT,                    -- Object attached to. Wikiname or Tkt UUID
  filename TEXT,                  -- Filename for the attachment
  comment TEXT,                   -- Comment associated with this attachment
  user TEXT                       -- Name of user adding attachment
);
CREATE INDEX repo.attachment_idx1 ON attachment(target, filename, mtime);
CREATE INDEX repo.attachment_idx2 ON attachment(src);

-- For tracking cherrypick merges
CREATE TABLE repo.cherrypick(
  parentid INT,
  childid INT,
  isExclude BOOLEAN DEFAULT false,
  PRIMARY KEY(parentid, childid)
) WITHOUT ROWID;
CREATE INDEX repo.cherrypick_cid ON cherrypick(childid);
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<












































































































































































































































































































































































































































Deleted sql/snippets.txt.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
A collection of SQL snippets and notes regarding the fossil db schema...


The following few are taken from an off-list email exchange:
========================================================================

The queries below are untested.  They are off the top of my head.
They might not work exactly right. There may well be faster ways.  But
perhaps these can get you started.

The very latest Fossil trunk is needed for the WITH RECURSIVE queries.
Or the latest SQLite trunk if you are going directly from a TCL
script.

 
(1) Find the leaf for branch X (specifically: trunk)

SELECT blob.uuid FROM leaf, blob, tag, tagxref
WHERE blob.rid=leaf.rid
AND tag.tagname='sym-'||$branchname
AND tagxref.tagid=tag.tagid
AND tagxref.tagtype>0
AND tagxref.rid=leaf.rid;
 
(2) Find the youngest revision tagged with T (specifically: release)

SELECT blob.uuid FROM blob, tag, tagxref, event
WHERE tag.tagname='sym-'||$tagname
AND tagxref.tagid=tag.tagid
AND tagxref.tagtype>0
AND event.objid=tag.rid
AND event.type='ci'
ORDER BY event.mtime DESC LIMIT 1;

 
(3) Find all revisions reachable from (1) via parent-links (primary
and merge!) coming after (2) timewise.
    Note that this definition
    = includes revisions on merged branches/forks, and
    = (2) is excluded from the set, and not necessarily reachable from (1)

/* All ancestors of node $uuid that occur after time $cutoff */
WITH RECURSIVE
  ancestors(rid) AS (
     SELECT rid FROM blob WHERE uuid=$uuid
     UNION
     SELECT pid FROM plink, ancestors, event
        WHERE plink.cid=ancestors.rid
             AND event.objid=ancestors.rid
             AND event.mtime>$cutoff
 )
SELECT blob.uuid FROM blob, ancestors
WHERE blob.rid=ancestors.rid;

 
(4) Per revision in the set of (3) determine the commit message.

/* All ancestors of node $uuid that occur after time $cutoff */
WITH RECURSIVE
  ancestors(rid) AS (
     SELECT rid FROM blob WHERE uuid=$uuid
     UNION
     SELECT pid FROM plink, ancestors, event
        WHERE plink.cid=ancestors.rid
             AND event.objid=ancestors.rid
             AND event.mtime>$cutoff
 )
SELECT blob.uuid, coalesce(event.ecomment,event.comment)
 FROM blob, ancestors, event
WHERE blob.rid=ancestors.rid
AND event.objid=ancestors.rid
ORDER BY event.mtime DESC;

 
(5) Per revision in the set of (3) determine the set of changed files
(edited/added/removed)

WITH RECURSIVE
  ancestors(rid) AS (
     SELECT rid FROM blob WHERE uuid=$uuid
     UNION
     SELECT pid FROM plink, ancestors, event
        WHERE plink.cid=ancestors.rid
             AND event.objid=ancestors.rid
             AND event.mtime>$cutoff
 )
SELECT
    blob.uuid,
    filename.name,
    CASE WHEN nullif(mlink.pid,0) is null THEN 'added'
        WHEN nullif(mlink.fid,0) is null THEN 'deleted'
        ELSE 'edited' END
 FROM blob, ancestors, event, mlink, filename
WHERE blob.rid=ancestors.rid
AND event.objid=ancestors.rid
AND mlink.mid=ancestor.rid
AND mlink.fnid=filename.fnid
ORDER BY event.mtime DESC, filename.name;

= end of untested bits
========================================================================
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










































































































































































































Deleted sql/ticket-reports.sql.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
INSERT INTO reportfmt(title,mtime,cols,sqlcode) 
VALUES('All Tickets',julianday('1970-01-01'),'#ffffff Key:
#f2dcdc Active
#e8e8e8 Review
#cfe8bd Fixed
#bde5d6 Tested
#cacae5 Deferred
#c8c8c8 Closed','SELECT
  CASE WHEN status IN (''Open'',''Verified'') THEN ''#f2dcdc''
       WHEN status=''Review'' THEN ''#e8e8e8''
       WHEN status=''Fixed'' THEN ''#cfe8bd''
       WHEN status=''Tested'' THEN ''#bde5d6''
       WHEN status=''Deferred'' THEN ''#cacae5''
       ELSE ''#c8c8c8'' END AS ''bgcolor'',
  substr(tkt_uuid,1,10) AS ''#'',
  datetime(tkt_mtime) AS ''mtime'',
  type,
  status,
  subsystem,
  title
FROM ticket');
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










































Deleted sql/ticket.sql.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
-- Template for the TICKET table
CREATE TABLE repo.ticket(
  -- Do not change any column that begins with tkt_
  tkt_id INTEGER PRIMARY KEY,
  tkt_uuid TEXT UNIQUE,
  tkt_mtime DATE,
  tkt_ctime DATE,
  -- Add as many field as required below this line
  type TEXT,
  status TEXT,
  subsystem TEXT,
  priority TEXT,
  severity TEXT,
  foundin TEXT,
  private_contact TEXT,
  resolution TEXT,
  title TEXT,
  comment TEXT
);
CREATE TABLE repo.ticketchng(
  -- Do not change any column that begins with tkt_
  tkt_id INTEGER REFERENCES ticket,
  tkt_rid INTEGER REFERENCES blob,
  tkt_mtime DATE,
  -- Add as many fields as required below this line
  login TEXT,
  username TEXT,
  mimetype TEXT,
  icomment TEXT
);
CREATE INDEX repo.ticketchng_idx1 ON ticketchng(tkt_id, tkt_mtime);
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






























































Changes to src/Makefile.in.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64


65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
all:
include ../subdir-inc.make
#$(error $(TOP_SRCDIR))
CPPFLAGS += -DBUILD_libfossil -DSQLITE_CORE
#CPPFLAGS += -I$(TOP_INCDIR)
#CPPFLAGS += -I$(TOP_SRCDIR)/include
#CPPFLAGS += -I$(TOP_SRCDIR)/src# workaround for in-tree sqlite3.h
EXTRA_LIBS := -lz
EXTRA_LIBS +=  $(LDFLAGS_MODULE_LOADER)

#INCLUDES_PATH ?= $(HOME)/include /usr/local/include /usr/include


# FSL.SRC.BASE = all sources which can go in the amalgamation build
# FSL.SRC = FSL.SRC.BASE + generated/added sources
FSL.SRC.BASE := \
	fsl.c \
	appendf.c \
	auth.c \
	bag.c \
	buffer.c \
	cache.c \
	checkin.c \
	checkout.c \
	cli.c \
	content.c \
	config.c \
	cx.c \
	db.c \
	deck.c \
	delta.c \
	diff.c \
	encode.c \
	event.c \
	ext_regexp.c \
	fs.c \
	forum.c \
	glob.c \
	io.c \
	leaf.c \
	list.c \
	md5.c \
	merge3.c \
	popen.c \
	pq.c \
	repo.c \
	schema.c \
	search.c \
	sha1.c \
	sha3.c \
	strftime.c \
	tag.c \
	ticket.c \
	utf8.c \
	vfile.c \
	vpath.c \
	wiki.c \
	zip.c

FSL.SRC := $(FSL.SRC.BASE)

ifneq (,$(wildcard sqlite3.c))
  FSL.SRC += sqlite3.c
  HAVE_OWN_SQLITE := 1


else
  HAVE_OWN_SQLITE := 0
endif

FSL.OBJ := $(patsubst %.c,%.o,$(FSL.SRC))

########################################################################
# Transform schema SQL files into source form. We don't use fossil(1)'s
# approach because it relies on unspecified length limits on C string
# literals. Yeah, it works, but i'm funny about the C Standard.
#include $(TOP_SRCDIR_REL)/tools.make
DIR.TOOLS := $(TOP_SRCDIR_REL)/tools
BIN.TEXT2C := $(DIR.TOOLS)/text2c
$(BIN.TEXT2C): $(DIR.TOOLS)/text2c.c
	cc $< -o $@
CLEAN_FILES += $(BIN.TEXT2C)

define SQL2C
$(2):
$(1).c: $(2) $$(BIN.TEXT2C) $$(MAIN_MAKEFILES)
	@echo "Creating $$@ from $(2)..."; \
	{ \
		echo '/* Binary form of file $(2) */'; \
		echo '/** @page page_$(1) Schema: $$(notdir $(2))'; \
		echo '@code'; \
		cat "$(2)"; \
		echo ' @endcode'; \
		echo ' @see $$(subst _cstr,,$(1))()'; \
		echo '*/'; \
		$$(BIN.TEXT2C) fsl_$(1) < $(2); \
		echo '/* end of $(2) */'; \
	} > $$@;
FSL.SRC += $(1).c
FSL.SRC.BASE += $(1).c
FSL.OBJ += $(1).o
DISTCLEAN_FILES += $(1).c
sql: $(1).c
endef

SQL.DIR := $(TOP_SRCDIR_REL)/sql
$(eval $(call SQL2C,schema_config_cstr,$(SQL.DIR)/config.sql))
$(eval $(call SQL2C,schema_repo1_cstr,$(SQL.DIR)/repo-static.sql))
$(eval $(call SQL2C,schema_repo2_cstr,$(SQL.DIR)/repo-transient.sql))
$(eval $(call SQL2C,schema_ckout_cstr,$(SQL.DIR)/checkout.sql))
$(eval $(call SQL2C,schema_ticket_cstr,$(SQL.DIR)/ticket.sql))
$(eval $(call SQL2C,schema_ticket_reports_cstr,$(SQL.DIR)/ticket-reports.sql))
$(eval $(call SQL2C,schema_forum_cstr,$(SQL.DIR)/forum.sql))
# end SQL transformation bits
########################################################################

# These are the flags used by fossil's embedded sqlite3:
sqlite3.o: CPPFLAGS+=-DNDEBUG=1 \
                 -DSQLITE_DQS=0 \
                 -DSQLITE_THREADSAFE=0 \
                 -DSQLITE_DEFAULT_MEMSTATUS=0 \
                 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 \



<
<
<
<
|
<

<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

<

>
>



<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







1
2
3




4

5

6

















































7

8
9
10
11
12
13












14

































15
16
17
18
19
20
21
all:
include ../subdir-inc.make
#$(error $(TOP_SRCDIR))




#EXTRA_LIBS := -lz



THE_OBJ := libfossil.o

















































ifneq (,$(wildcard sqlite3.c))

  HAVE_OWN_SQLITE := 1
  CPPFLAGS += -DSQLITE_CORE
  THE_OBJ += sqlite3.o
else
  HAVE_OWN_SQLITE := 0
endif












CLEAN_FILES += $(THE_OBJ)


































# These are the flags used by fossil's embedded sqlite3:
sqlite3.o: CPPFLAGS+=-DNDEBUG=1 \
                 -DSQLITE_DQS=0 \
                 -DSQLITE_THREADSAFE=0 \
                 -DSQLITE_DEFAULT_MEMSTATUS=0 \
                 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 \
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285

sqlite3.o: CFLAGS+=-Wno-sign-compare
ifneq (,$(filter clang,$(CC)))
# Workaround for clang
sqlite3.o: CFLAGS+=-Wno-unused-const-variable
endif

CLEAN_FILES += $(FSL.OBJ)
ifeq (1,$(HAVE_OWN_SQLITE))
# in-tree sqlite3
# -lm: one of the sqlite build opts (FTS?) requires log(3).
  EXTRA_LIBS += -lm
else
# system/external sqlite3
  EXTRA_LIBS += -lpthread
endif

libfossil.DLL.OBJECTS := $(FSL.OBJ)
libfossil.DLL.LDFLAGS := @SH_LDFLAGS@ $(EXTRA_LIBS)
libfossil.LIB.OBJECTS := $(libfossil.DLL.OBJECTS)
########################################################################
# Shared lib
ifeq (1,@LIBFOSSIL_SHARED@)
$(eval $(call ShakeNMake.CALL.RULES.DLLS,libfossil))
all: $(libfossil.DLL)
$(libfossil.DLL): $(libfossil.DLL.OBJECTS)
endif

# Static lib
ifeq (1,@LIBFOSSIL_STATIC@)
$(eval $(call ShakeNMake.CALL.RULES.LIBS,libfossil))
all: $(libfossil.LIB)
$(libfossil.LIB): $(libfossil.LIB.OBJECTS)
endif

########################################################################
# A quick-n-dirty amalgamation build...
INCD := $(TOP_INCDIR)/fossil-scm
AMAL_D := $(TOP_SRCDIR_REL)
AMAL_C := $(AMAL_D)/libfossil.c
AMAL_H := $(AMAL_D)/libfossil.h
AMAL_CONF.H := $(AMAL_D)/libfossil-config.h
CLEAN_FILES += $(AMAL_H) $(AMAL_C) $(AMAL_CONF.H)

############################################################
# AMAL_C.SRC = the list of source files (.c and private .h) to include
# in $(AMAL_C).
AMAL_C.SRC := \
	fossil-ext_regexp.h \
	$(FSL.SRC.BASE)

ifeq (0,1)
# the tcl bits currently break the amalgamation
AMAL_C.SRC += \
	$(COMPAT_DIR)/javavm/export/jni_md.h \
	$(COMPAT_DIR)/javavm/export/jni.h \
	$(COMPAT_DIR)/tcl-8.6/generic/tcl.h \
	$(COMPAT_DIR)/tcl-8.6/generic/tclDecls.h \
	$(COMPAT_DIR)/tcl-8.6/generic/tclPlatDecls.h
endif

############################################################
# AMAL_H.SRC = the list of public header files to include
# in $(AMAL_H).
AMAL_H.SRC := \
	$(INCD)/fossil-config.h \
	$(INCD)/fossil.h \
	$(INCD)/fossil-util.h \
	$(INCD)/fossil-core.h \
	$(INCD)/fossil-db.h \
	$(INCD)/fossil-hash.h \
	$(INCD)/fossil-repo.h \
	$(INCD)/fossil-checkout.h \
	$(INCD)/fossil-confdb.h \
	$(INCD)/fossil-vpath.h \
	$(INCD)/fossil-internal.h \
	$(INCD)/fossil-auth.h \
	$(INCD)/fossil-forum.h \
	$(INCD)/fossil-pages.h \
	$(INCD)/fossil-cli.h
$(AMAL_H.SRC):
$(AMAL_C.SRC):
$(AMAL_H): $(MAKEFILE_LIST) $(AMAL_H.SRC) $(AMAL_CONF.H)
$(AMAL_C): $(MAKEFILE_LIST) $(AMAL_H) $(AMAL_C.SRC)
$(AMAL_CONF.H): $(MAIN_MAKEFILES)
	@echo "Generating $@ ..."
	cd $(TOP_SRCDIR_REL) && sh configure --amal

$(AMAL_H): 
	@echo "Creating $@..."
	@{ \
		echo '#if !defined(FSL_AMALGAMATION_BUILD)'; \
		echo '#define FSL_AMALGAMATION_BUILD 1'; \
		echo '#endif'; \
		echo '#include "$(notdir $(AMAL_CONF.H))"'; \
	} > $@
	@{ \
		for i in $(AMAL_H.SRC); do \
			echo "/* start of file $$i */"; \
			cat $$i; \
			echo "/* end of file $$i */"; \
		done; \
	} | sed  \
		 -e '/[ ]*#[ ]*include[ ]*.*fossil.*\.h[>"]/d' \
		 -e '/[ ]*#[ ]*include[ ]*.*".*config\.h"/d' \
		>> $@

$(AMAL_C):
	@echo "Creating $@..."
	@echo '#include "$(notdir $(AMAL_H))"' > $@
	@{ \
		for i in $(AMAL_C.SRC); do \
			echo "/* start of file $$i */"; \
			cat $$i; \
			echo "/* end of file $$i */"; \
		done; \
	} | sed \
		 -e '/[ ]*#[ ]*include[ ]*.*fossil.*\.h[>"]/d' \
		>> $@

AMAL_CPP_FLAGS := -I$(TOP_INCDIR)
AMAL_CPP_FLAGS += -I.# for local sqlite3.h
amal: $(AMAL_C)
	@echo "Amalgamation files:"; \
	ls -la $(AMAL_C) $(AMAL_H) $(AMAL_CONF.H)
	@if which gcc >/dev/null; then \
		echo "Trying GCC C89..."; \
		gcc -c -pedantic -Wstrict-aliasing -Wall -Werror -std=c89 -Wno-long-long $(AMAL_CPP_FLAGS) $(AMAL_C) || exit;\
		echo "Trying GCC C99..."; \
		gcc -c -pedantic -Wstrict-aliasing -Wall -Werror -std=c99 $(AMAL_CPP_FLAGS) $(AMAL_C) || exit; \
	fi
	@if which tcc >/dev/null; then \
		echo "Trying tcc..."; \
		tcc -c -pedantic -Wall -Werror $(AMAL_CPP_FLAGS) $(AMAL_C) || exit; \
		echo "Man, that was FAST!"; \
	 fi
	@if which clang >/dev/null; then \
		echo "Trying clang..."; \
		clang -c -pedantic -Wall -Werror $(AMAL_CPP_FLAGS) $(AMAL_C) || exit; \
	fi
	@echo "Reminder: it will need these sqlite3 files to build:"; \
	echo "sqlite3.h, sqlite3ext.h, and optionally a local copy of sqlite3.c"
# /amalgamation
########################################################################







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
43
44
45
46
47
48
49

















50
























































































































sqlite3.o: CFLAGS+=-Wno-sign-compare
ifneq (,$(filter clang,$(CC)))
# Workaround for clang
sqlite3.o: CFLAGS+=-Wno-unused-const-variable
endif


















all: $(THE_OBJ)























































































































Deleted src/appendf.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/************************************************************************
The printf-like implementation in this file is based on the one found
in the sqlite3 distribution is in the Public Domain.

This copy was forked for use with the clob API in Feb 2008 by Stephan
Beal (https://wanderinghorse.net/home/stephan/) and modified to send
its output to arbitrary targets via a callback mechanism. Also
refactored the %X specifier handlers a bit to make adding/removing
specific handlers easier.

All code in this file is released into the Public Domain.

The printf implementation (fsl_appendfv()) is pretty easy to extend
(e.g. adding or removing %-specifiers for fsl_appendfv()) if you're
willing to poke around a bit and see how the specifiers are declared
and dispatched. For an example, grep for 'etSTRING' and follow it
through the process of declaration to implementation.

See below for several FSLPRINTF_OMIT_xxx macros which can be set to
remove certain features/extensions.

LICENSE:

  This program is free software; you can redistribute it and/or
  modify it under the terms of the Simplified BSD License (also
  known as the "2-Clause License" or "FreeBSD License".)

  This program is distributed in the hope that it will be useful,
  but without any warranty; without even the implied warranty of
  merchantability or fitness for a particular purpose.
**********************************************************************/

#include "fossil-scm/fossil-util.h"
#include <string.h> /* strlen() */
#include <ctype.h>
#include <assert.h>

/* FIXME: determine this type at compile time via configuration
   options. OTOH, it compiles everywhere as-is so far.
 */
typedef long double LONGDOUBLE_TYPE;

/*
  If FSLPRINTF_OMIT_FLOATING_POINT is defined to a true value, then
  floating point conversions are disabled.
*/
#ifndef FSLPRINTF_OMIT_FLOATING_POINT
#  define FSLPRINTF_OMIT_FLOATING_POINT 0
#endif

/*
  If FSLPRINTF_OMIT_SIZE is defined to a true value, then
  the %n specifier is disabled.
*/
#ifndef FSLPRINTF_OMIT_SIZE
#  define FSLPRINTF_OMIT_SIZE 0
#endif

/*
  If FSLPRINTF_OMIT_SQL is defined to a true value, then
  the %q, %Q, and %B specifiers are disabled.
*/
#ifndef FSLPRINTF_OMIT_SQL
#  define FSLPRINTF_OMIT_SQL 0
#endif

/*
  If FSLPRINTF_OMIT_HTML is defined to a true value then the %h (HTML
  escape), %t (URL escape), and %T (URL unescape) specifiers are
  disabled.
*/
#ifndef FSLPRINTF_OMIT_HTML
#  define FSLPRINTF_OMIT_HTML 0
#endif

/**
   If true, the %j (JSON string) format is enabled.
*/
#define FSLPRINTF_ENABLE_JSON 1

/*
  Most C compilers handle variable-sized arrays, so we enable
  that by default. Some (e.g. tcc) do not, so we provide a way
  to disable it: set FSLPRINTF_HAVE_VARARRAY to 0

  One approach would be to look at:

  defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)

  but some compilers support variable-sized arrays even when not
  explicitly running in c99 mode.
*/
#if !defined(FSLPRINTF_HAVE_VARARRAY)
#  if defined(__TINYC__)
#    define FSLPRINTF_HAVE_VARARRAY 0
#  else
#    if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
#        define FSLPRINTF_HAVE_VARARRAY 1 /*use 1 in C99 mode */
#    else
#        define FSLPRINTF_HAVE_VARARRAY 0
#    endif
#  endif
#endif

/*
  Conversion types fall into various categories as defined by the
  following enumeration.
*/
enum PrintfCategory {etRADIX = 1, /* Integer types.  %d, %x, %o, and so forth */
                     etFLOAT = 2, /* Floating point.  %f */
                     etEXP = 3, /* Exponentional notation. %e and %E */
                     etGENERIC = 4, /* Floating or exponential, depending on exponent. %g */
                     etSIZE = 5, /* Return number of characters processed so far. %n */
                     etSTRING = 6, /* Strings. %s */
                     etDYNSTRING = 7, /* Dynamically allocated strings. %z */
                     etPERCENT = 8, /* Percent symbol. %% */
                     etCHARX = 9, /* Characters. %c */
                     /* The rest are extensions, not normally found in printf() */
                     etCHARLIT = 10, /* Literal characters.  %' */
#if !FSLPRINTF_OMIT_SQL
                     etSQLESCAPE = 11, /* Strings with '\'' doubled.  %q */
                     etSQLESCAPE2 = 12, /* Strings with '\'' doubled and enclosed in '',
                                           NULL pointers replaced by SQL NULL.  %Q */
                     etSQLESCAPE3 = 16, /* %w -> Strings with '\"' doubled */
                     etBLOBSQL = 13, /* %B -> Works like %Q,
                                        but requires a (fsl_buffer*) argument. */
#endif /* !FSLPRINTF_OMIT_SQL */
                     etPOINTER = 15, /* The %p conversion */
                     etORDINAL = 17, /* %r -> 1st, 2nd, 3rd, 4th, etc.  English only */
#if ! FSLPRINTF_OMIT_HTML
                     etHTML = 18, /* %h -> basic HTML escaping. */
                     etURLENCODE = 19, /* %t -> URL encoding. */
                     etURLDECODE = 20, /* %T -> URL decoding. */
#endif
                     etPATH = 21, /* %/ -> replace '\\' with '/' in path-like strings. */
                     etBLOB = 22, /* Works like %s, but requires a (fsl_buffer*) argument. */
                     etFOSSILIZE = 23, /* %F => like %s, but fossilizes it. */
                     etSTRINGID = 24, /* String with length limit for a UUID prefix: %S */
#if FSLPRINTF_ENABLE_JSON
                     etJSONSTR = 25,
#endif
                     etPLACEHOLDER = 100
                     };

/*
  An "etByte" is an 8-bit unsigned value.
*/
typedef unsigned char etByte;

/*
  Each builtin conversion character (ex: the 'd' in "%d") is described
  by an instance of the following structure
*/
typedef struct et_info {   /* Information about each format field */
  char fmttype;            /* The format field code letter */
  etByte base;             /* The base for radix conversion */
  etByte flags;            /* One or more of FLAG_ constants below */
  etByte type;             /* Conversion paradigm */
  etByte charset;          /* Offset into aDigits[] of the digits string */
  etByte prefix;           /* Offset into aPrefix[] of the prefix string */
} et_info;

/*
  Allowed values for et_info.flags
*/
enum et_info_flags { FLAG_SIGNED = 1,    /* True if the value to convert is signed */
                     FLAG_EXTENDED = 2,  /* True if for internal/extended use only. */
                     FLAG_STRING = 4     /* Allow infinity precision */
};

/*
  Historically, the following table was searched linearly, so the most
  common conversions were kept at the front.

  Change 2008 Oct 31 by Stephan Beal: we reserve an array of ordered
  entries for all chars in the range [32..126]. Format character
  checks can now be done in constant time by addressing that array
  directly.  This takes more static memory, but reduces the time and
  per-call overhead costs of fsl_appendfv().
*/
static const char aDigits[] = "0123456789ABCDEF0123456789abcdef";
static const char aPrefix[] = "-x0\000X0";
static const et_info fmtinfo[] = {
/*
  These entries MUST stay in ASCII order, sorted
  on their fmttype member! They MUST start with
  fmttype==32 and end at fmttype==126.
*/
{' '/*32*/, 0, 0, 0, 0, 0 },
{'!'/*33*/, 0, 0, 0, 0, 0 },
{'"'/*34*/, 0, 0, 0, 0, 0 },
{'#'/*35*/, 0, 0, 0, 0, 0 },
{'$'/*36*/, 0, 0, 0, 0, 0 },
{'%'/*37*/, 0, 0, etPERCENT, 0, 0 },
{'&'/*38*/, 0, 0, 0, 0, 0 },
{'\''/*39*/, 0, 0, 0, 0, 0 },
{'('/*40*/, 0, 0, 0, 0, 0 },
{')'/*41*/, 0, 0, 0, 0, 0 },
{'*'/*42*/, 0, 0, 0, 0, 0 },
{'+'/*43*/, 0, 0, 0, 0, 0 },
{','/*44*/, 0, 0, 0, 0, 0 },
{'-'/*45*/, 0, 0, 0, 0, 0 },
{'.'/*46*/, 0, 0, 0, 0, 0 },
{'/'/*47*/, 0, 0, etPATH, 0, 0 },
{'0'/*48*/, 0, 0, 0, 0, 0 },
{'1'/*49*/, 0, 0, 0, 0, 0 },
{'2'/*50*/, 0, 0, 0, 0, 0 },
{'3'/*51*/, 0, 0, 0, 0, 0 },
{'4'/*52*/, 0, 0, 0, 0, 0 },
{'5'/*53*/, 0, 0, 0, 0, 0 },
{'6'/*54*/, 0, 0, 0, 0, 0 },
{'7'/*55*/, 0, 0, 0, 0, 0 },
{'8'/*56*/, 0, 0, 0, 0, 0 },
{'9'/*57*/, 0, 0, 0, 0, 0 },
{':'/*58*/, 0, 0, 0, 0, 0 },
{';'/*59*/, 0, 0, 0, 0, 0 },
{'<'/*60*/, 0, 0, 0, 0, 0 },
{'='/*61*/, 0, 0, 0, 0, 0 },
{'>'/*62*/, 0, 0, 0, 0, 0 },
{'?'/*63*/, 0, 0, 0, 0, 0 },
{'@'/*64*/, 0, 0, 0, 0, 0 },
{'A'/*65*/, 0, 0, 0, 0, 0 },
#if FSLPRINTF_OMIT_SQL
{'B'/*66*/, 0, 0, 0, 0, 0 },
#else
{'B'/*66*/, 0, 2, etBLOBSQL, 0, 0 },
#endif
{'C'/*67*/, 0, 0, 0, 0, 0 },
{'D'/*68*/, 0, 0, 0, 0, 0 },
{'E'/*69*/, 0, FLAG_SIGNED, etEXP, 14, 0 },
{'F'/*70*/, 0, 4, etFOSSILIZE, 0, 0 },
{'G'/*71*/, 0, FLAG_SIGNED, etGENERIC, 14, 0 },
{'H'/*72*/, 0, 0, 0, 0, 0 },
{'I'/*73*/, 0, 0, 0, 0, 0 },
{'J'/*74*/, 0, 0, 0, 0, 0 },
{'K'/*75*/, 0, 0, 0, 0, 0 },
{'L'/*76*/, 0, 0, 0, 0, 0 },
{'M'/*77*/, 0, 0, 0, 0, 0 },
{'N'/*78*/, 0, 0, 0, 0, 0 },
{'O'/*79*/, 0, 0, 0, 0, 0 },
{'P'/*80*/, 0, 0, 0, 0, 0 },
#if FSLPRINTF_OMIT_SQL
{'Q'/*81*/, 0, 0, 0, 0, 0 },
#else
{'Q'/*81*/, 0, FLAG_STRING, etSQLESCAPE2, 0, 0 },
#endif
{'R'/*82*/, 0, 0, 0, 0, 0 },
{'S'/*83*/, 0, FLAG_STRING, etSTRINGID, 0, 0 },
{'T'/*84*/, 0, FLAG_STRING, etURLDECODE, 0, 0 },
{'U'/*85*/, 0, 0, 0, 0, 0 },
{'V'/*86*/, 0, 0, 0, 0, 0 },
{'W'/*87*/, 0, 0, 0, 0, 0 },
{'X'/*88*/, 16, 0, etRADIX,      0,  4 },
{'Y'/*89*/, 0, 0, 0, 0, 0 },
{'Z'/*90*/, 0, 0, 0, 0, 0 },
{'['/*91*/, 0, 0, 0, 0, 0 },
{'\\'/*92*/, 0, 0, 0, 0, 0 },
{']'/*93*/, 0, 0, 0, 0, 0 },
{'^'/*94*/, 0, 0, 0, 0, 0 },
{'_'/*95*/, 0, 0, 0, 0, 0 },
{'`'/*96*/, 0, 0, 0, 0, 0 },
{'a'/*97*/, 0, 0, 0, 0, 0 },
{'b'/*98*/, 0, 2, etBLOB, 0, 0 },
{'c'/*99*/, 0, 0, etCHARX,      0,  0 },
{'d'/*100*/, 10, FLAG_SIGNED, etRADIX,      0,  0 },
{'e'/*101*/, 0, FLAG_SIGNED, etEXP,        30, 0 },
{'f'/*102*/, 0, FLAG_SIGNED, etFLOAT,      0,  0},
{'g'/*103*/, 0, FLAG_SIGNED, etGENERIC,    30, 0 },
{'h'/*104*/, 0, FLAG_STRING, etHTML, 0, 0 },
{'i'/*105*/, 10, FLAG_SIGNED, etRADIX,      0,  0},
#if FSLPRINTF_ENABLE_JSON
{'j'/*106*/, 0, 0, etJSONSTR, 0, 0 },
#else
{'j'/*106*/, 0, 0, 0, 0, 0 },
#endif
{'k'/*107*/, 0, 0, 0, 0, 0 },
{'l'/*108*/, 0, 0, 0, 0, 0 },
{'m'/*109*/, 0, 0, 0, 0, 0 },
{'n'/*110*/, 0, 0, etSIZE, 0, 0 },
{'o'/*111*/, 8, 0, etRADIX,      0,  2 },
{'p'/*112*/, 16, 0, etPOINTER, 0, 1 },
#if FSLPRINTF_OMIT_SQL
{'q'/*113*/, 0, 0, 0, 0, 0 },
#else
{'q'/*113*/, 0, FLAG_STRING, etSQLESCAPE,  0, 0 },
#endif
{'r'/*114*/, 10, (FLAG_EXTENDED|FLAG_SIGNED), etORDINAL,    0,  0},
{'s'/*115*/, 0, FLAG_STRING, etSTRING,     0,  0 },
{'t'/*116*/,  0, FLAG_STRING, etURLENCODE, 0, 0 },
{'u'/*117*/, 10, 0, etRADIX,      0,  0 },
{'v'/*118*/, 0, 0, 0, 0, 0 },
#if FSLPRINTF_OMIT_SQL
{'w'/*119*/, 0, 0, 0, 0, 0 },
#else
{'w'/*119*/, 0, FLAG_STRING, etSQLESCAPE3, 0, 0 },
#endif
{'x'/*120*/, 16, 0, etRADIX,      16, 1  },
{'y'/*121*/, 0, 0, 0, 0, 0 },
{'z'/*122*/, 0, FLAG_STRING, etDYNSTRING,  0,  0},
{'{'/*123*/, 0, 0, 0, 0, 0 },
{'|'/*124*/, 0, 0, 0, 0, 0 },
{'}'/*125*/, 0, 0, 0, 0, 0 },
{'~'/*126*/, 0, 0, 0, 0, 0 }
};
#define etNINFO  (sizeof(fmtinfo)/sizeof(fmtinfo[0]))

#if ! FSLPRINTF_OMIT_FLOATING_POINT
/*
  "*val" is a double such that 0.1 <= *val < 10.0
  Return the ascii code for the leading digit of *val, then
  multiply "*val" by 10.0 to renormalize.
    
  Example:
  input:     *val = 3.14159
  output:    *val = 1.4159    function return = '3'
    
  The counter *cnt is incremented each time.  After counter exceeds
  16 (the number of significant digits in a 64-bit float) '0' is
  always returned.
*/
static int et_getdigit(LONGDOUBLE_TYPE *val, int *cnt){
  int digit;
  LONGDOUBLE_TYPE d;
  if( (*cnt)++ >= 16 ) return '0';
  digit = (int)*val;
  d = digit;
  digit += '0';
  *val = (*val - d)*10.0;
  return digit;
}
#endif /* !FSLPRINTF_OMIT_FLOATING_POINT */

/*
  On machines with a small(?) stack size, you can redefine the
  FSLPRINTF_BUF_SIZE to be less than 350.  But beware - for smaller
  values some %f conversions may go into an infinite loop.
*/
#ifndef FSLPRINTF_BUF_SIZE
#  define FSLPRINTF_BUF_SIZE 350  /* Size of the output buffer for numeric conversions */
#endif

#if defined(FSL_INT_T_PFMT)
/* int64_t is already defined. */
#else
#if ! defined(__STDC__) && !defined(__TINYC__)
#ifdef FSLPRINTF_INT64_TYPE
typedef FSLPRINTF_INT64_TYPE int64_t;
typedef unsigned FSLPRINTF_INT64_TYPE uint64_t;
#elif defined(_MSC_VER) || defined(__BORLANDC__)
typedef __int64 int64_t;
typedef unsigned __int64 uint64_t;
#else
typedef long long int int64_t;
typedef unsigned long long int uint64_t;
#endif
#endif
#endif
/* Set up of int64 type */

#if 0
/   Not yet used. */
enum PrintfArgTypes {
TypeInt = 0,
TypeIntP = 1,
TypeFloat = 2,
TypeFloatP = 3,
TypeCString = 4
};
#endif


#if 0
/   Not yet used. */
typedef struct fsl_appendf_spec_handler_def
{
  char letter; /   e.g. %s */
  int xtype; /* reference to the etXXXX values, or fmtinfo[*].type. */
  int ntype; /* reference to PrintfArgTypes enum. */
} spec_handler;
#endif

/**
   fsl_appendf_spec_handler is an almost-generic interface for farming
   work out of fsl_appendfv()'s code into external functions.  It doesn't
   actually save much (if any) overall code, but it makes the fsl_appendfv()
   code more manageable.


   REQUIREMENTS of implementations:

   - Expects an implementation-specific vargp pointer.
   fsl_appendfv() passes a pointer to the converted value of
   an entry from the format va_list. If it passes a type
   other than the expected one, undefined results.

   - If it calls pf it must do: pf( pfArg, D, N ), where D is
   the data to export and N is the number of bytes to export.
   It may call pf() an arbitrary number of times

   - If pf() successfully is called, the return value must be the
   accumulated totals of its return value(s), plus (possibly, but
   unlikely) an implementation-specific amount.

   - If it does not call pf() then it must return 0 (success)
   or a negative number (an error) or do all of the export
   processing itself and return the number of bytes exported.

   SIGNIFICANT LIMITATIONS:

   - Has no way of iterating over the format string, so handling
   precisions and such here can't work too well. (Nevermind:
   precision/justification is handled in fsl_appendfv().)
*/
typedef fsl_int_t (*fsl_appendf_spec_handler)( fsl_appendf_f pf,
                                               void * pfArg,
                                               unsigned int pfLen,
                                               void * vargp );


/**
   fsl_appendf_spec_handler for etSTRING types. It assumes that varg
   is a NUL-terminated (char [const] *)
*/
static fsl_int_t spech_string( fsl_appendf_f pf,
                               void * pfArg,
                               unsigned int pfLen,
                               void * varg )
{
  char const * ch = (char const *) varg;
  return ch ? pf( pfArg, ch, pfLen ) : 0;
}

/**
   fsl_appendf_spec_handler for etDYNSTRING types.  It assumes that
   varg is a non-const (char *). It behaves identically to
   spec_string() and then calls fsl_free() on that (char *).
*/
static fsl_int_t spech_dynstring( fsl_appendf_f pf,
                                  void * pfArg,
                                  unsigned int pfLen,
                                  void * varg )
{
  fsl_int_t ret = spech_string( pf, pfArg, pfLen, varg );
  fsl_free( varg );
  return ret;
}

#if !FSLPRINTF_OMIT_HTML
static fsl_int_t spech_string_to_html( fsl_appendf_f pf,
                                       void * pfArg,
                                       unsigned int pfLen,
                                       void * varg )
{
  char const * ch = (char const *) varg;
  unsigned int i;
  fsl_int_t ret = 0;
  if( ! ch ) return 0;
  ret = 0;
  for( i = 0; (i<pfLen) && *ch; ++ch, ++i )
  {
    switch( *ch )
    {
      case '<': ret += pf( pfArg, "&lt;", 4 );
        break;
      case '&': ret += pf( pfArg, "&amp;", 5 );
        break;
      default:
        ret += pf( pfArg, ch, 1 );
        break;
    };
  }
  return ret;
}

static int httpurl_needs_escape( int c )
{
  /*
    Definition of "safe" and "unsafe" chars
    was taken from:

    https://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4029/
  */
  return ( (c >= 32 && c <=47)
           || ( c>=58 && c<=64)
           || ( c>=91 && c<=96)
           || ( c>=123 && c<=126)
           || ( c<32 || c>=127)
           );
}

/**
   The handler for the etURLENCODE specifier.

   It expects varg to be a string value, which it will preceed to
   encode using an URL encoding algothrim (certain characters are
   converted to %XX, where XX is their hex value) and passes the
   encoded string to pf(). It returns the total length of the output
   string.
*/
static fsl_int_t spech_urlencode( fsl_appendf_f pf,
                                  void * pfArg,
                                  unsigned int pfLen,
                                  void * varg )
{
  char const * str = (char const *) varg;
  fsl_int_t ret = 0;
  char ch = 0;
  char const * hex = "0123456789ABCDEF";
#define xbufsz 10
  char xbuf[xbufsz];
  int slen = 0;
  if( ! str ) return 0;
  memset( xbuf, 0, xbufsz );
  ch = *str;
#define xbufsz 10
  slen = 0;
  for( ; ch; ch = *(++str) )
  {
    if( ! httpurl_needs_escape( ch ) )
    {
      ret += pf( pfArg, str, 1 );
      continue;
    }
    else {
      xbuf[0] = '%';
      xbuf[1] = hex[((ch>>4)&0xf)];
      xbuf[2] = hex[(ch&0xf)];
      xbuf[3] = 0;
      slen = 3;
      ret += pf( pfArg, xbuf, slen );
    }
  }
#undef xbufsz
  return ret;
}

/* 
   hexchar_to_int():

   For 'a'-'f', 'A'-'F' and '0'-'9', returns the appropriate decimal
   number.  For any other character it returns -1.
*/
static int hexchar_to_int( int ch )
{
  if( (ch>='0' && ch<='9') ) return ch-'0';
  else if( (ch>='a' && ch<='f') ) return ch-'a'+10;
  else if( (ch>='A' && ch<='F') ) return ch-'A'+10;
  else return -1;
}

/**
   The handler for the etURLDECODE specifier.

   It expects varg to be a ([const] char *), possibly encoded
   with URL encoding. It decodes the string using a URL decode
   algorithm and passes the decoded string to
   pf(). It returns the total length of the output string.
   If the input string contains malformed %XX codes then this
   function will return prematurely.
*/
static fsl_int_t spech_urldecode( fsl_appendf_f pf,
                                  void * pfArg,
                                  unsigned int pfLen,
                                  void * varg )
{
  char const * str = (char const *) varg;
  fsl_int_t ret = 0;
  char ch = 0;
  char ch2 = 0;
  char xbuf[4];
  int decoded;
  if( ! str ) return 0;
  ch = *str;
  while( ch )
  {
    if( ch == '%' )
    {
      ch = *(++str);
      ch2 = *(++str);
      if( isxdigit((int)ch) &&
          isxdigit((int)ch2) )
      {
        decoded = (hexchar_to_int( ch ) * 16)
          + hexchar_to_int( ch2 );
        xbuf[0] = (char)decoded;
        xbuf[1] = 0;
        ret += pf( pfArg, xbuf, 1 );
        ch = *(++str);
        continue;
      }
      else
      {
        xbuf[0] = '%';
        xbuf[1] = ch;
        xbuf[2] = ch2;
        xbuf[3] = 0;
        ret += pf( pfArg, xbuf, 3 );
        ch = *(++str);
        continue;
      }
    }
    else if( ch == '+' )
    {
      xbuf[0] = ' ';
      xbuf[1] = 0;
      ret += pf( pfArg, xbuf, 1 );
      ch = *(++str);
      continue;
    }
    xbuf[0] = ch;
    xbuf[1] = 0;
    ret += pf( pfArg, xbuf, 1 );
    ch = *(++str);
  }
  return ret;
}

#endif /* !FSLPRINTF_OMIT_HTML */


#if !FSLPRINTF_OMIT_SQL
/**
   Quotes the (char *) varg as an SQL string 'should'
   be quoted. The exact type of the conversion
   is specified by xtype, which must be one of
   etSQLESCAPE, etSQLESCAPE2, or etSQLESCAPE3.

   Search this file for those constants to find
   the associated documentation.
*/
static fsl_int_t spech_sqlstring_main( int xtype,
                                       fsl_appendf_f pf,
                                       void * pfArg,
                                       unsigned int pfLen,
                                       void * varg )
{
  unsigned int i, n;
  int j, ch, isnull;
  int needQuote;
  char q = ((xtype==etSQLESCAPE3)?'"':'\'');   /* Quote character */
  char const * escarg = (char const *) varg;
  char * bufpt = NULL;
  fsl_int_t ret;
  isnull = escarg==0;
  if( isnull ) escarg = (xtype==etSQLESCAPE2 ? "NULL" : "(NULL)");
  for(i=n=0; (i<pfLen) && ((ch=escarg[i])!=0); ++i){
    if( ch==q ) ++n;
  }
  needQuote = !isnull && xtype==etSQLESCAPE2;
  n += i + 1 + needQuote*2;
  /* FIXME: use a static buffer here instead of malloc()! Shame on you! */
  bufpt = (char *)fsl_malloc( n );
  if( ! bufpt ) return -1;
  j = 0;
  if( needQuote ) bufpt[j++] = q;
  for(i=0; (ch=escarg[i])!=0; i++){
    bufpt[j++] = ch;
    if( ch==q ) bufpt[j++] = ch;
  }
  if( needQuote ) bufpt[j++] = q;
  bufpt[j] = 0;
  ret = pf( pfArg, bufpt, j );
  fsl_free( bufpt );
  return ret;
}

static fsl_int_t spech_sqlstring1( fsl_appendf_f pf,
                                   void * pfArg,
                                   unsigned int pfLen,
                                   void * varg )
{
  return spech_sqlstring_main( etSQLESCAPE, pf, pfArg, pfLen, varg );
}

static fsl_int_t spech_sqlstring2( fsl_appendf_f pf,
                                   void * pfArg,
                                   unsigned int pfLen,
                                   void * varg )
{
  return spech_sqlstring_main( etSQLESCAPE2, pf, pfArg, pfLen, varg );
}

static fsl_int_t spech_sqlstring3( fsl_appendf_f pf,
                                   void * pfArg,
                                   unsigned int pfLen,
                                   void * varg )
{
  return spech_sqlstring_main( etSQLESCAPE3, pf, pfArg, pfLen, varg );
}

#endif /* !FSLPRINTF_OMIT_SQL */

#if 0
static fsl_int_t spech_blob2( fsl_appendf_f pf,
                              void * pfArg,
                              unsigned int pfLen,
                              void * varg )
{
  fsl_buffer * b = (fsl_buffer *)varg;
  return spech_sqlstring_main( etSQLESCAPE3, pf, pfArg, pfLen, varg );
}
#endif

#if FSLPRINTF_ENABLE_JSON
/* TODO? Move these UTF8 bits into the public API? */
/*
** This lookup table is used to help decode the first byte of
** a multi-byte UTF8 character.
**
** Taken from sqlite3:
** https://www.sqlite.org/src/artifact?ln=48-61&name=810fbfebe12359f1
*/
static const unsigned char fsl_utfTrans1[] = {
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
  0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
  0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
  0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00
};
unsigned int fsl_utf8_read_char(
  const unsigned char *zIn,       /* First byte of UTF-8 character */
  const unsigned char *zTerm,     /* Pretend this byte is 0x00 */
  const unsigned char **pzNext    /* Write first byte past UTF-8 char here */
){
  /*
    Adapted from sqlite3:
    https://www.sqlite.org/src/artifact?ln=155-165&name=810fbfebe12359f1
  */
  unsigned c;
  if(zIn>=zTerm){
    *pzNext = zTerm;
    c = 0;
  }else{
    c = (unsigned int)*(zIn++);
    if( c>=0xc0 ){
      c = fsl_utfTrans1[c-0xc0];
      while( zIn!=zTerm && (*zIn & 0xc0)==0x80 )
        c = (c<<6) + (0x3f & *(zIn++));
      if( c<0x80
          || (c&0xFFFFF800)==0xD800
          || (c&0xFFFFFFFE)==0xFFFE ) c = 0xFFFD;
    }
    *pzNext = zIn;
  }
  return c;
}

static int fsl_utf8_char_to_cstr(unsigned int c, unsigned char *output, unsigned char length){
    /* Stolen from the internet, adapted from several variations which
      all _seem_ to have derived from librdf. */
    unsigned char size=0;

    /* check for illegal code positions:
     * U+D800 to U+DFFF (UTF-16 surrogates)
     * U+FFFE and U+FFFF
     */
    if((c > 0xD7FF && c < 0xE000)
       || c == 0xFFFE || c == 0xFFFF) return -1;

    /* Unicode 3.2 only defines U+0000 to U+10FFFF and UTF-8 encodings of it */
    if(c > 0x10ffff) return -1;
    
    if (c < 0x00000080) size = 1;
    else if (c < 0x00000800) size = 2;
    else if (c < 0x00010000) size = 3;
    else size = 4;
    if(!output) return (int)size;
    else if(size > length) return -1;
    else switch(size) {
      case 0:
          assert(!"can't happen anymore");
          output[0] = 0;
          return 0;
      case 4:
          output[3] = 0x80 | (c & 0x3F);
          c = c >> 6;
          c |= 0x10000;
          /* Fall through */
      case 3:
          output[2] = 0x80 | (c & 0x3F);
          c = c >> 6;
          c |= 0x800;
          /* Fall through */
      case 2:
          output[1] = 0x80 | (c & 0x3F);
          c = c >> 6;
          c |= 0xc0; 
          /* Fall through */
      case 1:
        output[0] = (unsigned char)c;
          /* Fall through */
      default:
        return (int)size;
    }
}

struct SpechJson {
  char const * z;
  bool addQuotes;
  bool escapeSmallUtf8;
};

/**
   fsl_appendf_spec_handler for etJSONSTR. It assumes that varg is a
   SpechJson struct instance.
*/
static fsl_int_t spech_json( fsl_appendf_f pf, void * pfArg,
                             unsigned int pfLen, void * varg )
{
  struct SpechJson const * state = (struct SpechJson *)varg;
  fsl_int_t pfRcTmp = 0;
  fsl_int_t pfRc = 0;
  const unsigned char *z = (const unsigned char *)state->z;
  const unsigned char *zEnd = z + pfLen;
  const unsigned char * zNext = 0;
  unsigned int c;
  unsigned char c1;

#define out(X,N) pfRcTmp=pf(pfArg, (char const *)(X), N); \
  if(pfRcTmp<0) return pfRcTmp; pfRc+=pfRcTmp
#define outc c1 = (unsigned char)c; out(&c1,1)
  if(!z){
    out("null",4);
    return pfRc;
  }    
  if(state->addQuotes){
    out("\"", 1);
  }
  for( ; (z < zEnd) && (c=fsl_utf8_read_char(z, zEnd, &zNext));
       z = zNext ){
    if( c=='\\' || c=='"' ){
      out("\\", 1);
      outc;
    }else if( c<' ' ){
      out("\\",1);
      if( c=='\n' ){
        out("n",1);
      }else if( c=='\r' ){
        out("r",1);
      }else{
        unsigned char ubuf[5] = {'u',0,0,0,0};
        int i;
        for(i = 4; i>0; --i){
          ubuf[i] = "0123456789abcdef"[c&0xf];
          c >>= 4;
        }
        out(ubuf,5);
      }
    }else if(c<128){
      outc;
    }/* At this point we know that c is part of a multi-byte
        character. We're assuming legal UTF8 input, which means
        emitting a surrogate pair if the value is > 0xffff. */
    else if(c<0xFFFF){
      unsigned char ubuf[6];
      if(state->escapeSmallUtf8){
        /* Output char in \u#### form. */
        fsl_snprintf((char *)ubuf, 6, "\\u%04x", c);
        out(ubuf, 6);
      }else{
        /* Output character literal. */
        int const n = fsl_utf8_char_to_cstr(c, ubuf, 4);
        if(n<0){
          out("?",1);
        }else{
          assert(n>0);
          out(ubuf, n);
        }
      }
    }else{
      /* Surrogate pair. */
      unsigned char ubuf[12];
      c -= 0x10000;
      fsl_snprintf((char *)ubuf, 12, "\\u%04x\\u%04x",
                   (0xd800 | (c>>10)),
                   (0xdc00 | (c & 0x3ff)));
      out(ubuf, 12);
    }
  }
  if(state->addQuotes){
    out("\"",1);
  }
  return pfRc;
#undef out
#undef outc
}
#endif /* FSLPRINTF_ENABLE_JSON */

/*
   Find the length of a string as long as that length does not
   exceed N bytes.  If no zero terminator is seen in the first
   N bytes then return N.  If N is negative, then this routine
   is an alias for strlen().
*/
static int StrNLen32(const char *z, int N){
  int n = 0;
  while( (N-- != 0) && *(z++)!=0 ){ n++; }
  return n;
}

/*
  The root printf program.  All variations call this core.  It
  implements most of the common printf behaviours plus (optionally)
  some extended ones.

  INPUTS:

  pfAppend : The is a fsl_appendf_f function which is responsible
  for accumulating the output. If pfAppend returns a negative integer
  then processing stops immediately.

  pfAppendArg : is ignored by this function but passed as the first
  argument to pfAppend. pfAppend will presumably use it as a data
  store for accumulating its string.

  fmt : This is the format string, as in the usual printf().

  ap : This is a pointer to a list of arguments.  Same as in
  vprintf() and friends.

  OUTPUTS:

  The return value is the total number value from all calls to
  pfAppend.

  Note that the order in which automatic variables are declared below
  seems to make a big difference in determining how fast this beast
  will run.

  Much of this code dates back to the early 1980's, supposedly.

  Known change history (most historic info has been lost):

  10 Feb 2008 by Stephan Beal: refactored to remove the 'useExtended'
  flag (which is now always on). Added the fsl_appendf_f typedef to
  make this function generic enough to drop into other source trees
  without much work.

  31 Oct 2008 by Stephan Beal: refactored the et_info lookup to be
  constant-time instead of linear.
*/
fsl_int_t fsl_appendfv(fsl_appendf_f pfAppend, /* Accumulate results here */
                       void * pfAppendArg,     /* Passed as first arg to pfAppend. */
                       const char *fmt,        /* Format string */
                       va_list ap              /* arguments */
                       ){
  /**
     HISTORIC NOTE (author and year unknown):

     Note that the order in which automatic variables are declared below
     seems to make a big difference in determining how fast this beast
     will run.
  */

  fsl_int_t outCount = 0;          /* accumulated output count */
  int pfrc = 0;              /* result from calling pfAppend */
  int c;                     /* Next character in the format string */
  char *bufpt = 0;           /* Pointer to the conversion buffer */
  int precision;             /* Precision of the current field */
  int length;                /* Length of the field */
  int idx;                   /* A general purpose loop counter */
  int width;                 /* Width of the current field */
  etByte flag_leftjustify;   /* True if "-" flag is present */
  etByte flag_plussign;      /* True if "+" flag is present */
  etByte flag_blanksign;     /* True if " " flag is present */
  etByte flag_alternateform; /* True if "#" flag is present */
  etByte flag_altform2;      /* True if "!" flag is present */
  etByte flag_zeropad;       /* True if field width constant starts with zero */
  etByte flag_long;          /* True if "l" flag is present */
  etByte flag_longlong;      /* True if the "ll" flag is present */
  etByte done;               /* Loop termination flag */
  etByte cThousand           /* Thousands separator for %d and %u */
    /* ported in from https://fossil-scm.org/home/info/2cdbdbb1c9b7ad2b */;
  uint64_t longvalue;        /* Value for integer types */
  LONGDOUBLE_TYPE realvalue; /* Value for real types */
  const et_info *infop = 0;      /* Pointer to the appropriate info structure */
  char buf[FSLPRINTF_BUF_SIZE];       /* Conversion buffer */
  char prefix;               /* Prefix character.  "+" or "-" or " " or '\0'. */
  etByte xtype = 0;              /* Conversion paradigm */
  char * zExtra = 0;              /* Extra memory used for etTCLESCAPE conversions */
#if ! FSLPRINTF_OMIT_FLOATING_POINT
  int  exp, e2;              /* exponent of real numbers */
  double rounder;            /* Used for rounding floating point values */
  etByte flag_dp;            /* True if decimal point should be shown */
  etByte flag_rtz;           /* True if trailing zeros should be removed */
  etByte flag_exp;           /* True to force display of the exponent */
  int nsd;                   /* Number of significant digits returned */
#endif


/**
   FSLPRINTF_CHARARRAY is a helper to allocate variable-sized arrays.
   This exists mainly so this code can compile with the tcc compiler.
*/
#if FSLPRINTF_HAVE_VARARRAY
#  define FSLPRINTF_CHARARRAY(V,N) char V[N+1]; memset(V,0,N+1)
#  define FSLPRINTF_CHARARRAY_FREE(V)
#else
#  define FSLPRINTF_CHARARRAY_STACK(V) 
#  define FSLPRINTF_CHARARRAY(V,N) char V##2[256]; \
  char * V;                                                      \
  if((int)(N)<((int)sizeof(V##2))){                              \
    V = V##2;                                   \
  }else{                                        \
    V = (char *)fsl_malloc(N+1);       \
    if(!V) {FSLPRINTF_RETURN;}         \
  }
#  define FSLPRINTF_CHARARRAY_FREE(V) if(V!=V##2) fsl_free(V)
#endif

  /* FSLPRINTF_RETURN, FSLPRINTF_CHECKERR, and FSLPRINTF_SPACES
     are internal helpers.
  */
#define FSLPRINTF_RETURN if( zExtra ) fsl_free(zExtra); return outCount
#define FSLPRINTF_CHECKERR if( pfrc<0 ) { FSLPRINTF_RETURN; } else outCount += pfrc
#define FSLPRINTF_SPACES(N)                     \
  {                                             \
    FSLPRINTF_CHARARRAY(zSpaces,N);             \
    memset( zSpaces,' ',N);                     \
    pfrc = pfAppend(pfAppendArg, zSpaces, N);   \
    FSLPRINTF_CHARARRAY_FREE(zSpaces);          \
    FSLPRINTF_CHECKERR;                \
  } (void)0

  length = 0;
  bufpt = 0;
  for(; (c=(*fmt))!=0; ++fmt){
    if( c!='%' ){
      int amt;
      bufpt = (char *)fmt;
      amt = 1;
      while( (c=(*++fmt))!='%' && c!=0 ) amt++;
      pfrc = pfAppend( pfAppendArg, bufpt, amt);
      FSLPRINTF_CHECKERR;
      if( c==0 ) break;
    }
    if( (c=(*++fmt))==0 ){
      pfrc = pfAppend( pfAppendArg, "%", 1);
      FSLPRINTF_CHECKERR;
      break;
    }
    /* Find out what flags are present */
    flag_leftjustify = flag_plussign = flag_blanksign = cThousand =
      flag_alternateform = flag_altform2 = flag_zeropad = 0;
    done = 0;
    do{
      switch( c ){
        case '-':   flag_leftjustify = 1;     break;
        case '+':   flag_plussign = 1;        break;
        case ' ':   flag_blanksign = 1;       break;
        case '#':   flag_alternateform = 1;   break;
        case '!':   flag_altform2 = 1;        break;
        case '0':   flag_zeropad = 1;         break;
        case ',':   cThousand = ',';          break;
        default:    done = 1;                 break;
      }
    }while( !done && (c=(*++fmt))!=0 );
    /* Get the field width */
    width = 0;
    if( c=='*' ){
      width = va_arg(ap,int);
      if( width<0 ){
        flag_leftjustify = 1;
        width = width >= -2147483647 ? -width : 0;
      }
      c = *++fmt;
    }else{
      unsigned wx = 0;
      while( c>='0' && c<='9' ){
        wx = wx * 10 + c - '0';
        width = width*10 + c - '0';
        c = *++fmt;
      }
      width = wx & 0x7fffffff;
    }
    if( width > FSLPRINTF_BUF_SIZE-10 ){
      width = FSLPRINTF_BUF_SIZE-10;
    }
    /* Get the precision */
    if( c=='.' ){
      precision = 0;
      c = *++fmt;
      if( c=='*' ){
        precision = va_arg(ap,int);
        c = *++fmt;
        if( precision<0 ){
          precision = precision >= -2147483647 ? -precision : -1;
        }

      }else{
        unsigned px = 0;
        while( c>='0' && c<='9' ){
          px = px*10 + c - '0';
          c = *++fmt;
        }
        precision = px & 0x7fffffff;
      }
    }else{
      precision = -1;
    }
    /* Get the conversion type modifier */
    if( c=='l' ){
      flag_long = 1;
      c = *++fmt;
      if( c=='l' ){
        flag_longlong = 1;
        c = *++fmt;
      }else{
        flag_longlong = 0;
      }
    }else{
      flag_long = flag_longlong = 0;
    }
    /* Fetch the info entry for the field */
    infop = 0;
#define FMTNDX(N) (N - fmtinfo[0].fmttype)
#define FMTINFO(N) (fmtinfo[ FMTNDX(N) ])
    infop = ((c>=(fmtinfo[0].fmttype)) && (c<fmtinfo[etNINFO-1].fmttype))
      ? &FMTINFO(c)
      : 0;
    /*fprintf(stderr,"char '%c'/%d @ %d,  type=%c/%d\n",c,c,FMTNDX(c),infop->fmttype,infop->type);*/
    if( infop ) xtype = infop->type;
#undef FMTINFO
#undef FMTNDX
    zExtra = 0;
    if( (!infop) || (!infop->type) ){
      FSLPRINTF_RETURN;
    }


    /* Limit the precision to prevent overflowing buf[] during conversion */
    if( precision>FSLPRINTF_BUF_SIZE-40 && (infop->flags & FLAG_STRING)==0 ){
      precision = FSLPRINTF_BUF_SIZE-40;
    }

    /*
      At this point, variables are initialized as follows:
        
      flag_alternateform          TRUE if a '#' is present.
      flag_altform2               TRUE if a '!' is present.
      flag_plussign               TRUE if a '+' is present.
      flag_leftjustify            TRUE if a '-' is present or if the
                                  field width was negative.
      flag_zeropad                TRUE if the width began with 0.
      flag_long                   TRUE if the letter 'l' (ell) prefixed
                                  the conversion character.
      flag_longlong               TRUE if the letter 'll' (ell ell) prefixed
                                  the conversion character.
      flag_blanksign              TRUE if a ' ' is present.
      width                       The specified field width.  This is
                                  always non-negative.  Default is 0.
      precision                   The specified precision.  The default
                                  is -1.
      xtype                       The class of the conversion.
      infop                       Pointer to the appropriate info struct.
    */
    switch( xtype ){
      case etPOINTER:
        flag_longlong = sizeof(char*)==sizeof(int64_t);
        flag_long = sizeof(char*)==sizeof(long int);
        /* Fall through into the next case */
      case etORDINAL:
      case etRADIX:
        if( infop->flags & FLAG_SIGNED ){
          int64_t v;
          if( flag_longlong )   v = va_arg(ap,int64_t);
          else if( flag_long )  v = va_arg(ap,long int);
          else                  v = va_arg(ap,int);
          if( v<0 ){
            longvalue = -v;
            prefix = '-';
          }else{
            longvalue = v;
            if( flag_plussign )        prefix = '+';
            else if( flag_blanksign )  prefix = ' ';
            else                       prefix = 0;
          }
        }else{
          if( flag_longlong )   longvalue = va_arg(ap,uint64_t);
          else if( flag_long )  longvalue = va_arg(ap,unsigned long int);
          else                  longvalue = va_arg(ap,unsigned int);
          prefix = 0;
        }
        if( longvalue==0 ) flag_alternateform = 0;
        if( flag_zeropad && precision<width-(prefix!=0) ){
          precision = width-(prefix!=0);
        }
        bufpt = &buf[FSLPRINTF_BUF_SIZE-1];
        if( xtype==etORDINAL ){
          /** i sure would like to shake the hand of whoever figured this out: */
          static const char zOrd[] = "thstndrd";
          int x = longvalue % 10;
          if( x>=4 || (longvalue/10)%10==1 ){
            x = 0;
          }
          buf[FSLPRINTF_BUF_SIZE-3] = zOrd[x*2];
          buf[FSLPRINTF_BUF_SIZE-2] = zOrd[x*2+1];
          bufpt -= 2;
        }
        {
          const char *cset;
          int base;
          cset = &aDigits[infop->charset];
          base = infop->base;
          do{                                           /* Convert to ascii */
            *(--bufpt) = cset[longvalue%base];
            longvalue = longvalue/base;
          }while( longvalue>0 );
        }
        length = &buf[FSLPRINTF_BUF_SIZE-1]-bufpt;
        while( precision>length ){
          *(--bufpt) = '0';                             /* Zero pad */
          ++length;
        }
        if( cThousand ){
          int nn = (length - 1)/3;  /* Number of "," to insert */
          int ix = (length - 1)%3 + 1;
          bufpt -= nn;
          for(idx=0; nn>0; idx++){
            bufpt[idx] = bufpt[idx+nn];
            ix--;
            if( ix==0 ){
              bufpt[++idx] = cThousand;
              nn--;
              ix = 3;
            }
          }
        }
        if( prefix ) *(--bufpt) = prefix;               /* Add sign */
        if( flag_alternateform && infop->prefix ){      /* Add "0" or "0x" */
          const char *pre;
          char x;
          pre = &aPrefix[infop->prefix];
          if( *bufpt!=pre[0] ){
            for(; (x=(*pre))!=0; pre++) *(--bufpt) = x;
          }
        }
        length = &buf[FSLPRINTF_BUF_SIZE-1]-bufpt;
        break;
      case etFLOAT:
      case etEXP:
      case etGENERIC:
        realvalue = va_arg(ap,double);
#if ! FSLPRINTF_OMIT_FLOATING_POINT
        if( precision<0 ) precision = 6;         /* Set default precision */
        if( precision>FSLPRINTF_BUF_SIZE/2-10 ) precision = FSLPRINTF_BUF_SIZE/2-10;
        if( realvalue<0.0 ){
          realvalue = -realvalue;
          prefix = '-';
        }else{
          if( flag_plussign )          prefix = '+';
          else if( flag_blanksign )    prefix = ' ';
          else                         prefix = 0;
        }
        if( xtype==etGENERIC && precision>0 ) precision--;
#if 0
        /* Rounding works like BSD when the constant 0.4999 is used.  Wierd! */
        for(idx=precision & 0xfff, rounder=0.4999; idx>0; idx--, rounder*=0.1);
#else
        /* It makes more sense to use 0.5 */
        for(idx=precision & 0xfff, rounder=0.5; idx>0; idx--, rounder*=0.1){}
#endif
        if( xtype==etFLOAT ) realvalue += rounder;
        /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */
        exp = 0;
#if 1
        if( (realvalue)!=(realvalue) ){
          /* from sqlite3: #define sqlite3_isnan(X)  ((X)!=(X)) */
          /* This weird array thing is to avoid constness violations
             when assinging, e.g. "NaN" to bufpt.
          */
          static char NaN[4] = {'N','a','N','\0'};
          bufpt = NaN;
          length = 3;
          break;
        }
#endif
        if( realvalue>0.0 ){
          while( realvalue>=1e32 && exp<=350 ){ realvalue *= 1e-32; exp+=32; }
          while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; }
          while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; }
          while( realvalue<1e-8 && exp>=-350 ){ realvalue *= 1e8; exp-=8; }
          while( realvalue<1.0 && exp>=-350 ){ realvalue *= 10.0; exp--; }
          if( exp>350 || exp<-350 ){
            if( prefix=='-' ){
              static char Inf[5] = {'-','I','n','f','\0'};
              bufpt = Inf;
            }else if( prefix=='+' ){
              static char Inf[5] = {'+','I','n','f','\0'};
              bufpt = Inf;
            }else{
              static char Inf[4] = {'I','n','f','\0'};
              bufpt = Inf;
            }
            length = strlen(bufpt);
            break;
          }
        }
        bufpt = buf;
        /*
          If the field type is etGENERIC, then convert to either etEXP
          or etFLOAT, as appropriate.
        */
        flag_exp = xtype==etEXP;
        if( xtype!=etFLOAT ){
          realvalue += rounder;
          if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; }
        }
        if( xtype==etGENERIC ){
          flag_rtz = !flag_alternateform;
          if( exp<-4 || exp>precision ){
            xtype = etEXP;
          }else{
            precision = precision - exp;
            xtype = etFLOAT;
          }
        }else{
          flag_rtz = 0;
        }
        if( xtype==etEXP ){
          e2 = 0;
        }else{
          e2 = exp;
        }
        nsd = 0;
        flag_dp = (precision>0) | flag_alternateform | flag_altform2;
        /* The sign in front of the number */
        if( prefix ){
          *(bufpt++) = prefix;
        }
        /* Digits prior to the decimal point */
        if( e2<0 ){
          *(bufpt++) = '0';
        }else{
          for(; e2>=0; e2--){
            *(bufpt++) = et_getdigit(&realvalue,&nsd);
          }
        }
        /* The decimal point */
        if( flag_dp ){
          *(bufpt++) = '.';
        }
        /* "0" digits after the decimal point but before the first
           significant digit of the number */
        for(e2++; e2<0 && precision>0; precision--, e2++){
          *(bufpt++) = '0';
        }
        /* Significant digits after the decimal point */
        while( (precision--)>0 ){
          *(bufpt++) = et_getdigit(&realvalue,&nsd);
        }
        /* Remove trailing zeros and the "." if no digits follow the "." */
        if( flag_rtz && flag_dp ){
          while( bufpt[-1]=='0' ) *(--bufpt) = 0;
          /* assert( bufpt>buf ); */
          if( bufpt[-1]=='.' ){
            if( flag_altform2 ){
              *(bufpt++) = '0';
            }else{
              *(--bufpt) = 0;
            }
          }
        }
        /* Add the "eNNN" suffix */
        if( flag_exp || (xtype==etEXP && exp) ){
          *(bufpt++) = aDigits[infop->charset];
          if( exp<0 ){
            *(bufpt++) = '-'; exp = -exp;
          }else{
            *(bufpt++) = '+';
          }
          if( exp>=100 ){
            *(bufpt++) = (exp/100)+'0';                /* 100's digit */
            exp %= 100;
          }
          *(bufpt++) = exp/10+'0';                     /* 10's digit */
          *(bufpt++) = exp%10+'0';                     /* 1's digit */
        }
        *bufpt = 0;

        /* The converted number is in buf[] and zero terminated. Output it.
           Note that the number is in the usual order, not reversed as with
           integer conversions. */
        length = bufpt-buf;
        bufpt = buf;

        /* Special case:  Add leading zeros if the flag_zeropad flag is
           set and we are not left justified */
        if( flag_zeropad && !flag_leftjustify && length < width){
          int i;
          int nPad = width - length;
          for(i=width; i>=nPad; i--){
            bufpt[i] = bufpt[i-nPad];
          }
          i = prefix!=0;
          while( nPad-- ) bufpt[i++] = '0';
          length = width;
        }
#endif /* !FSLPRINTF_OMIT_FLOATING_POINT */
        break;
#if !FSLPRINTF_OMIT_SIZE
      case etSIZE:
        *(va_arg(ap,int*)) = outCount;
        length = width = 0;
        break;
#endif
      case etPERCENT:
        buf[0] = '%';
        bufpt = buf;
        length = 1;
        break;
      case etCHARLIT:
      case etCHARX:
        c = buf[0] = (xtype==etCHARX ? va_arg(ap,int) : *++fmt);
        if( precision>=0 ){
          for(idx=1; idx<precision; idx++) buf[idx] = c;
          length = precision;
        }else{
          length =1;
        }
        bufpt = buf;
        break;
      case etPATH: {
        /* Sanitize path-like inputs, replacing \\ with /. */
        int i;
        int limit = flag_alternateform ? va_arg(ap,int) : -1;
        char const *e = va_arg(ap,char const*);
        if( e && *e ){
          length = StrNLen32(e, limit);
          zExtra = bufpt = fsl_malloc(length+1);
          if(!zExtra) return -1;
          for( i=0; i<length; i++ ){
            if( e[i]=='\\' ){
              bufpt[i]='/';
            }else{
              bufpt[i]=e[i];
            }
          }
          bufpt[length]='\0';
        }
        break;
      }
      case etSTRINGID: {
        precision = flag_altform2 ? -1 : 16
          /* In fossil(1) this is configurable, but in this lib we
             don't have access to that state from here. Fossil also
             has the '!' flag_altform2, which indicates that it
             should be for a URL, and thus longer than the default.
             We are only roughly approximating that behaviour here. */;
        /* Fall through */
      }
      case etSTRING: {
        bufpt = va_arg(ap,char*);
        length = bufpt ? strlen(bufpt) : 0;
        if( precision>=0 && precision<length ) length = precision;
        break;
      }
      case etDYNSTRING: {
        /* etDYNSTRING needs to be handled separately because it
           free()s its argument (which isn't available outside this
           block). This means, though, that %-#z does not work.
        */
        bufpt = va_arg(ap,char*);
        length = bufpt ? strlen(bufpt) : 0;
        pfrc = spech_dynstring( pfAppend, pfAppendArg,
                                (precision>=0 && precision<length)
                                ? precision : length,
                                bufpt );
        bufpt = NULL;
        FSLPRINTF_CHECKERR;
        length = 0;
        break;
      }
      case etBLOB: {
        /* int const limit = flag_alternateform ? va_arg(ap, int) : -1; */
        fsl_buffer *pBlob = va_arg(ap, fsl_buffer*);
        bufpt = fsl_buffer_str(pBlob);
        length = (int)fsl_buffer_size(pBlob);
        if( precision>=0 && precision<length ) length = precision;
        /* if( limit>=0 && limit<length ) length = limit; */
        break;
      }
      case etFOSSILIZE:{
        int const limit = -1; /*flag_alternateform ? va_arg(ap,int) : -1;*/
        fsl_buffer fb = fsl_buffer_empty;
        int check;
        bufpt = va_arg(ap,char*);
        length = bufpt ? (int)fsl_strlen(bufpt) : 0;
        if((limit>=0) && (length>limit)) length = limit;
        check = fsl_bytes_fossilize((unsigned char const *)bufpt, length, &fb);
        if(check){
          fsl_buffer_reserve(&fb,0);
          FSLPRINTF_RETURN;
        }
        zExtra = bufpt = (char*)fb.mem
          /*transfer ownership*/;
        length = (int)fb.used;
        if( precision>=0 && precision<length ) length = precision;
        break;
      }
#if FSLPRINTF_ENABLE_JSON
      case etJSONSTR: {
        struct SpechJson state;
        bufpt = va_arg(ap,char *);
        length = bufpt ? (int)fsl_strlen(bufpt) : 0;
        state.z = bufpt;
        state.addQuotes = flag_altform2 ? true : false;
        state.escapeSmallUtf8 = flag_alternateform ? true : false;
        pfrc = spech_json( pfAppend, pfAppendArg, (unsigned)length, &state );
        bufpt = NULL;
        FSLPRINTF_CHECKERR;
        length = 0;
        break;
      }
#endif
#if ! FSLPRINTF_OMIT_HTML
      case etHTML:{
        bufpt = va_arg(ap,char*);
        length = bufpt ? (int)fsl_strlen(bufpt) : 0;
        pfrc = spech_string_to_html( pfAppend, pfAppendArg,
                                     (precision<length) ? precision : length,
                                     bufpt );
        bufpt = NULL;
        FSLPRINTF_CHECKERR;
        length = 0;
        break;
      }
      case etURLENCODE:{
        bufpt = va_arg(ap,char*);
        length = bufpt ? (int)fsl_strlen(bufpt) : 0;
        pfrc = spech_urlencode( pfAppend, pfAppendArg,
                                (precision<length) ? precision : length,
                                bufpt );
        bufpt = NULL;
        FSLPRINTF_CHECKERR;
        length = 0;
        break;
      }
      case etURLDECODE:{
        bufpt = va_arg(ap,char*);
        length = bufpt ? (int)fsl_strlen(bufpt) : 0;
        bufpt = NULL;
        pfrc = spech_urldecode( pfAppend, pfAppendArg,
                                (precision<length) ? precision : length,
                                bufpt );
        FSLPRINTF_CHECKERR;
        length = 0;
        break;
      }
#endif /* FSLPRINTF_OMIT_HTML */
#if ! FSLPRINTF_OMIT_SQL
      case etBLOBSQL:
      case etSQLESCAPE:
      case etSQLESCAPE2:
      case etSQLESCAPE3: {
        fsl_appendf_spec_handler spf =
          (xtype==etSQLESCAPE)
          ? spech_sqlstring1
          : (((xtype==etSQLESCAPE2)||(etBLOBSQL==xtype))
             ? spech_sqlstring2
             : spech_sqlstring3
             );
        if(etBLOBSQL==xtype){
          fsl_buffer * b = va_arg(ap,fsl_buffer*);
          bufpt = fsl_buffer_str(b);
          length = (int)fsl_buffer_size(b);
        }else{
          bufpt = va_arg(ap,char*);
          length = bufpt ? strlen(bufpt) : 0;
        }
        pfrc = spf( pfAppend, pfAppendArg,
                    (precision<length) ? precision : length,
                    bufpt );
        FSLPRINTF_CHECKERR;
        length = 0;
      }
#endif /* !FSLPRINTF_OMIT_SQL */
    }/* End switch over the format type */
    /*
      The text of the conversion is pointed to by "bufpt" and is
      "length" characters long.  The field width is "width".  Do
      the output.
    */
    if( !flag_leftjustify ){
      int nspace;
      nspace = width-length;
      if( nspace>0 ){
        FSLPRINTF_SPACES(nspace);
      }
    }
    if( length>0 ){
      pfrc = pfAppend( pfAppendArg, bufpt, length);
      FSLPRINTF_CHECKERR;
    }
    if( flag_leftjustify ){
      int nspace;
      nspace = width-length;
      if( nspace>0 ){
        FSLPRINTF_SPACES(nspace);
      }
    }
    if( zExtra ){
      fsl_free(zExtra);
      zExtra = 0;
    }
  }/* End for loop over the format string */
  FSLPRINTF_RETURN;
} /* End of function */


#undef FSLPRINTF_CHARARRAY_STACK
#undef FSLPRINTF_CHARARRAY
#undef FSLPRINTF_CHARARRAY_FREE
#undef FSLPRINTF_SPACES
#undef FSLPRINTF_CHECKERR
#undef FSLPRINTF_RETURN
#undef FSLPRINTF_OMIT_FLOATING_POINT
#undef FSLPRINTF_OMIT_SIZE
#undef FSLPRINTF_OMIT_SQL
#undef FSLPRINTF_BUF_SIZE
#undef FSLPRINTF_OMIT_HTML

fsl_int_t fsl_appendf(fsl_appendf_f pfAppend, void * pfAppendArg,
                      const char *fmt, ... ){
  fsl_int_t ret;
  va_list vargs;
  va_start( vargs, fmt );
  ret = fsl_appendfv( pfAppend, pfAppendArg, fmt, vargs );
  va_end(vargs);
  return ret;
}


/*
   fsl_appendf_f() impl which requires that state be-a writable
   (FILE*).
*/
fsl_int_t fsl_appendf_f_FILE( void * state,
                              char const * s, fsl_int_t n ){
  if( !state ) return -1;
  else return (1==fwrite( s, (size_t)n, 1, (FILE *)state ))
    ? n : -2;
}

fsl_int_t fsl_fprintfv( FILE * fp, char const * fmt, va_list args ){
  return (fp && fmt)
    ? fsl_appendfv( fsl_appendf_f_FILE, fp, fmt, args )
    :  -1;
}

fsl_int_t fsl_fprintf( FILE * fp, char const * fmt, ... ){
  if(!fp || !fmt) return -1;
  else {
    fsl_int_t ret;
    va_list vargs;
    va_start( vargs, fmt );
    ret = fsl_appendfv( fsl_appendf_f_FILE, fp, fmt, vargs );
    va_end(vargs);
    return ret;
  }
}

char * fsl_mprintfv( char const * fmt, va_list vargs ){
  if( !fmt ) return 0;
  else if(!*fmt) return fsl_strndup("",0);
  else{
    fsl_buffer buf = fsl_buffer_empty;
    int const rc = fsl_buffer_appendfv( &buf, fmt, vargs );
    if(rc){
      fsl_buffer_reserve(&buf, 0);
      assert(0==buf.mem);
    }
    return (char*)buf.mem /*transfer ownership*/;
  }
}

char * fsl_mprintf( char const * fmt, ... ){
  char * ret;
  va_list vargs;
  va_start( vargs, fmt );
  ret = fsl_mprintfv( fmt, vargs );
  va_end( vargs );
  return ret;
}


/**
    Internal state for fsl_snprintfv().
 */
struct fsl_snp_state {
  /** Destination memory */
  char * dest;
  /** Current output position in this->dest. */
  fsl_size_t pos;
  /** Length of this->dest. */
  fsl_size_t len;
};
typedef struct fsl_snp_state fsl_snp_state;

static fsl_int_t fsl_appendf_f_snprintf( void * arg,
                                         char const * data,
                                         fsl_int_t n ){
  fsl_snp_state * st = (fsl_snp_state*) arg;
  assert(n>=0);
  if(n==0 || (st->pos >= st->len)) return 0;
  else if((n + st->pos) > st->len){
    n = st->len - st->pos;
  }
  memcpy(st->dest + st->pos, data, (fsl_size_t)n);
  st->pos += n;
  assert(st->pos <= st->len);
  return n;
}

fsl_int_t fsl_snprintfv( char * dest, fsl_size_t n,
                         char const * fmt, va_list args){
  fsl_snp_state st = {NULL,0,0};
  fsl_int_t rc;
  if(!dest || !fmt) return -1;
  else if(!n || !*fmt){
    if(dest) *dest = 0;
    return 0;
  }
  st.len = n;
  st.dest = dest;
  rc = fsl_appendfv( fsl_appendf_f_snprintf, &st, fmt, args );
  if(st.pos < st.len){
    dest[st.pos] = 0;
  }
  assert( rc <= (fsl_int_t)n );
  return rc;
}

fsl_int_t fsl_snprintf( char * dest, fsl_size_t n, char const * fmt, ... ){
  fsl_int_t rc;
  va_list vargs;
  va_start( vargs, fmt );
  rc = fsl_snprintfv( dest, n, fmt, vargs );
  va_end( vargs );
  return rc;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/auth.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 contains routines related to working with user authentication.
*/
#include "fossil-scm/fossil-internal.h"
#include "fossil-scm/fossil-hash.h"
#include "fossil-scm/fossil-confdb.h"


FSL_EXPORT char * fsl_sha1_shared_secret( fsl_cx * f, char const * zLoginName,
                                          char const * zPw ){
    if(!f || !zPw || !zLoginName) return 0;
    else{
        fsl_sha1_cx hash = fsl_sha1_cx_empty;
        unsigned char zResult[20];
        char zDigest[41];
        if(!f->cache.projectCode){
            f->cache.projectCode = fsl_config_get_text(f, FSL_CONFDB_REPO,
                                                       "project-code", 0);
            /*
              fossil(1) returns a copy of zPw here if !f->cache.projectCode,
              with the following comment:
            */
            /* On the first xfer request of a clone, the project-code is not yet
            ** known.  Use the cleartext password, since that is all we have.
            */
            if(!f->cache.projectCode) return 0;
        }
        fsl_sha1_update(&hash, f->cache.projectCode,
                        fsl_strlen(f->cache.projectCode));
        fsl_sha1_update(&hash, "/", 1);
        fsl_sha1_update(&hash, zLoginName, fsl_strlen(zLoginName));
        fsl_sha1_update(&hash, "/", 1);
        fsl_sha1_update(&hash, zPw, fsl_strlen(zPw));
        fsl_sha1_final(&hash, zResult);
        fsl_sha1_digest_to_base16(zResult, zDigest);
        return fsl_strndup( zDigest, FSL_STRLEN_SHA1 );
    }
}

FSL_EXPORT char * fsl_repo_login_group_name(fsl_cx * f){
  return f
    ? fsl_config_get_text(f, FSL_CONFDB_REPO,
                          "login-group-name", 0)
    : 0;
}

FSL_EXPORT char * fsl_repo_login_cookie_name(fsl_cx * f){
  fsl_db * db;
  if(!f || !(db = fsl_cx_db_repo(f))) return 0;
  else{
    char const * sql =
      "SELECT 'fossil-' || substr(value,1,16)"
      "  FROM config"
      " WHERE name IN ('project-code','login-group-code')"
      " ORDER BY name /*sort*/";
    return fsl_db_g_text(db, 0, sql);
  }
}

FSL_EXPORT int fsl_repo_login_search_uid(fsl_cx * f, char const * zUsername,
                                         char const * zPasswd,
                                         fsl_id_t * userId){
  int rc;
  char * zSecret;
  fsl_db * db;
  if(!f || !userId
     || !zUsername || !*zUsername
     || !zPasswd /*??? || !*zPasswd*/){
    return FSL_RC_MISUSE;
  }
  else if(!(db = fsl_needs_repo(f))){
    return FSL_RC_NOT_A_REPO;
  }
  *userId = 0;
  zSecret = fsl_sha1_shared_secret(f, zUsername, zPasswd );
  if(!zSecret) return FSL_RC_OOM;
  rc = fsl_db_get_id(db, userId,
                     "SELECT uid FROM user"
                     " WHERE login=%Q"
                     "   AND length(cap)>0 AND length(pw)>0"
                     "   AND login NOT IN ('anonymous','nobody','developer','reader')"
                     "   AND (pw=%Q OR (length(pw)<>40 AND pw=%Q))",
                     zUsername, zSecret, zPasswd);
  fsl_free(zSecret);
  return rc;
}

FSL_EXPORT int fsl_repo_login_clear( fsl_cx * f, fsl_id_t userId ){
  fsl_db * db;
  if(!f) return FSL_RC_MISUSE;
  else if(!(db = fsl_needs_repo(f))) return FSL_RC_NOT_A_REPO;
  else{
    int const rc = fsl_db_exec(db,
                       "UPDATE user SET cookie=NULL, ipaddr=NULL, "
                       " cexpire=0 WHERE "
                       " CASE WHEN %"FSL_ID_T_PFMT">=0 THEN uid=%"FSL_ID_T_PFMT""
                       " ELSE uid>0 END"
                       " AND login NOT IN('anonymous','nobody',"
                       " 'developer','reader')",
                       (fsl_id_t)userId, (fsl_id_t)userId);
    if(rc){
      fsl_cx_uplift_db_error(f, db);
    }
    return rc;
  }
}

#undef MARKER
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


















































































































































































































































Deleted src/bag.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 houses the code for the fsl_id_bag class.
*/
#include <assert.h>
#include <string.h> /* memset() */
#include "fossil-scm/fossil.h"
#include "fossil-scm/fossil-internal.h"

const fsl_id_bag fsl_id_bag_empty = fsl_id_bag_empty_m;

/* Only for debugging */
#include <stdio.h>

void fsl_id_bag_clear(fsl_id_bag *p){
  if(p->list) fsl_free(p->list);
  *p = fsl_id_bag_empty;
}

/*
   The hash function
*/
#define fsl_id_bag_hash(i)  (i*101)

/*
   Change the size of the hash table on a bag so that
   it contains N slots
  
   Completely reconstruct the hash table from scratch.  Deleted
   entries (indicated by a -1) are removed. When finished,
   p->entryCount==p->used and p->capacity==newSize.

   Returns on on success, FSL_RC_OOM on allocation error.
*/
static int fsl_id_bag_resize(fsl_id_bag *p, fsl_size_t newSize){
  fsl_size_t i;
  fsl_id_bag old;
  fsl_size_t nDel = 0;   /* Number of deleted entries */
  fsl_size_t nLive = 0;  /* Number of live entries */
  fsl_id_t * newList;
  assert( newSize > p->entryCount );
  newList = (fsl_id_t*)fsl_malloc( sizeof(p->list[0])*newSize );
  if(!newList) return FSL_RC_OOM;
  old = *p;
  p->list = newList;
  p->capacity = newSize;
  memset(p->list, 0, sizeof(p->list[0])*newSize );
  for(i=0; i<old.capacity; i++){
    fsl_id_t e = old.list[i];
    if( e>0 ){
      unsigned h = fsl_id_bag_hash(e)%newSize;
      while( p->list[h] ){
        h++;
        if( h==newSize ) h = 0;
      }
      p->list[h] = e;
      nLive++;
    }else if( e<0 ){
      nDel++;
    }
  }
  assert( p->entryCount == nLive );
  assert( p->used == nLive+nDel );
  p->used = p->entryCount;
  fsl_id_bag_clear(&old);
  return 0;
}

void fsl_id_bag_reset(fsl_id_bag *p){
  p->entryCount = p->used = 0;
}


int fsl_id_bag_insert(fsl_id_bag *p, fsl_id_t e){
  fsl_size_t h;
  int rc = 0;
  assert( e>0 );
  if( p->used+1 >= p->capacity/2 ){
    fsl_size_t n = p->capacity ? p->capacity*2 : 30;
    rc = fsl_id_bag_resize(p,  n );
    if(rc) return rc;
  }
  h = fsl_id_bag_hash(e)%p->capacity;
  while( p->list[h]>0 && p->list[h]!=e ){
    h++;
    if( h>=p->capacity ) h = 0;
  }
  if( p->list[h]<=0 ){
    if( p->list[h]==0 ) ++p->used;
    p->list[h] = e;
    ++p->entryCount;
    rc = 0;
  }
  return rc;
}

bool fsl_id_bag_contains(fsl_id_bag const *p, fsl_id_t e){
  fsl_size_t h;
  assert( e>0 );
  if( p->capacity==0 || 0==p->used ){
    return false;
  }
  assert(p->list);
  h = fsl_id_bag_hash(e)%p->capacity;
  while( p->list[h] && p->list[h]!=e ){
    h++;
    if( h>=p->capacity ) h = 0
      /*loop around to the start*/
      ;
  }
  return p->list[h]==e;
}

bool fsl_id_bag_remove(fsl_id_bag *p, fsl_id_t e){
  fsl_size_t h;
  bool rv = false;
  assert( e>0 );
  if( !p->capacity || !p->used ) return rv;
  assert(p->list);
  h = fsl_id_bag_hash(e)%p->capacity;
  while( p->list[h] && p->list[h]!=e ){
    ++h;
    if( h>=p->capacity ) h = 0;
  }
  rv = p->list[h]==e;
  if( p->list[h] ){
    fsl_size_t nx = h+1;
    if( nx>=p->capacity ) nx = 0;
    if( p->list[nx]==0 ){
      p->list[h] = 0;
      --p->used;
    }else{
      p->list[h] = -1;
    }
    --p->entryCount;
    if( p->entryCount==0 ){
      memset(p->list, 0, p->capacity*sizeof(p->list[0]));
      p->used = 0;
    }else if( p->capacity>40 && p->entryCount<p->capacity/8 ){
      fsl_id_bag_resize(p, p->capacity/2)
        /* ignore realloc error and keep the old size. */;
    }
  }
  return rv;
}

fsl_id_t fsl_id_bag_first(fsl_id_bag const *p){
  if( p->capacity==0 || 0==p->used ){
    return 0;
  }else{
    fsl_size_t i;
    for(i=0; i<p->capacity && p->list[i]<=0; ++i){}
    if( i<p->capacity ){
      return p->list[i];
    }else{
      return 0;
    }
  }
}

fsl_id_t fsl_id_bag_next(fsl_id_bag const *p, fsl_id_t e){
  fsl_size_t h;
  assert( p->capacity>0 );
  assert( e>0 );
  assert(p->list);
  h = fsl_id_bag_hash(e)%p->capacity;
  while( p->list[h] && p->list[h]!=e ){
    ++h;
    if( h>=p->capacity ) h = 0;
  }
  assert( p->list[h] );
  h++;
  while( h<p->capacity && p->list[h]<=0 ){
    h++;
  }
  return h<p->capacity ? p->list[h] : 0;
}

fsl_size_t fsl_id_bag_count(fsl_id_bag const *p){
  return p->entryCount;
}

void fsl_id_bag_swap(fsl_id_bag *lhs, fsl_id_bag *rhs){
  fsl_id_bag x = *lhs;
  *lhs = *rhs;
  *rhs = x;
}

#undef fsl_id_bag_hash
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
















































































































































































































































































































































































































Deleted src/buffer.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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).
*/

#include "fossil-scm/fossil.h"
#include <assert.h>
#include <string.h> /* strlen() */
#include <stddef.h> /* NULL on linux */
#include <errno.h>

#include <zlib.h>


#define MARKER(pfexp)                                               \
  do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__);   \
    printf pfexp;                                                   \
  } while(0)


fsl_buffer * fsl_buffer_reuse( fsl_buffer * b ){
  if(b->capacity){
    assert(b->mem);
    b->mem[0] = 0;
  }
  b->used = b->cursor = 0;
  return b;
}

void fsl_buffer_clear( fsl_buffer * buf ){
  if(buf){
    if(buf->mem) fsl_free(buf->mem);
    *buf = fsl_buffer_empty;
  }
}

int fsl_buffer_reserve( fsl_buffer * buf, fsl_size_t n ){
  if( ! buf ) return FSL_RC_MISUSE;
  else if( 0 == n ){
    fsl_free(buf->mem);
    *buf = fsl_buffer_empty;
    return 0;
  }else if( buf->capacity >= n ){
    return 0;
  }else{
    unsigned char * x;
    assert((buf->used < n) && "Buffer in-use greater than capacity!");
    x = (unsigned char *)fsl_realloc( buf->mem, n );
    if( ! x ) return FSL_RC_OOM;
    memset( x + buf->used, 0, n - buf->used );
    buf->mem = x;
    buf->capacity = n;
    return 0;
  }
}

int fsl_buffer_resize( fsl_buffer * buf, fsl_size_t n ){
  if( !buf ) return FSL_RC_MISUSE;
  else if(n && (buf->capacity == n+1)){
    buf->used = n;
    buf->mem[n] = 0;
    return 0;
  }else{
    unsigned char * x = (unsigned char *)fsl_realloc( buf->mem,
                                                      n+1/*NUL*/ );
    if( ! x ) return FSL_RC_OOM;
    if(n > buf->capacity){
      /* zero-fill new parts */
      memset( x + buf->capacity, 0, n - buf->capacity +1/*NUL*/ );
    }
    buf->capacity = n + 1 /*NUL*/;
    buf->used = n;
    buf->mem = x;
    buf->mem[buf->used] = 0;
    return 0;
  }
}

int fsl_buffer_compare(fsl_buffer const * lhs, fsl_buffer const * rhs){
  fsl_size_t const szL = lhs->used;
  fsl_size_t const szR = rhs->used;
  fsl_size_t const sz = (szL<szR) ? szL : szR;
  int rc = memcmp(lhs->mem, rhs->mem, sz);
  if(0 == rc){
    rc = (szL==szR)
      ? 0
      : ((szL<szR) ? -1 : 1);
  }
  return rc;
}

/*
   Compare two blobs in constant time and return zero if they are equal.
   Constant time comparison only applies for blobs of the same length.
   If lengths are different, immediately returns 1.
*/
int fsl_buffer_compare_O1(fsl_buffer const * lhs, fsl_buffer const * rhs){
  fsl_size_t const szL = lhs->used;
  fsl_size_t const szR = rhs->used;
  fsl_size_t i;
  unsigned char const *buf1;
  unsigned char const *buf2;
  unsigned char rc = 0;
  if( szL!=szR || szL==0 ) return 1;
  buf1 = lhs->mem;
  buf2 = rhs->mem;
  for( i=0; i<szL; i++ ){
    rc = rc | (buf1[i] ^ buf2[i]);
  }
  return rc;
}


int fsl_buffer_append( fsl_buffer * b,
                       void const * data,
                       fsl_int_t len ){
  if(!b || !data) return FSL_RC_MISUSE;
  else{
    fsl_size_t sz = b->used;
    int rc = 0;
    assert(b->capacity ? !!b->mem : !b->mem);
    assert(b->used <= b->capacity);
    if(len<0){
      len = (fsl_int_t)fsl_strlen((char const *)data);
    }
    sz += len + 1/*NUL*/;
    rc = fsl_buffer_reserve( b, sz );
    if(!rc){
      assert(b->capacity >= sz);
      if(len>0) memcpy(b->mem + b->used, data, (size_t)len);
      b->used += len;
      b->mem[b->used] = 0;
    }
    return rc;
  }
}

/*
   Internal helper for implementing fsl_buffer_appendf()
*/
typedef struct BufferAppender {
  fsl_buffer * b;
  /*
     Result code of the appending process.
  */
  int rc;
} BufferAppender;

/*
   fsl_appendf_f() impl which requires arg to be a (fsl_buffer*).
   It appends the data to arg. Returns the number of bytes written
   on success, a negative value on error. Always NUL-terminates the
   buffer on success.
*/
static fsl_int_t fsl_appendf_f_buffer( void * arg,
                                       char const * data, fsl_int_t n ){
  BufferAppender * ba = (BufferAppender*)arg;
  fsl_buffer * sb = ba->b;
  if( !sb || (n<0) ) return -1;
  else if( ! n ) return 0;
  else{
    fsl_int_t rc;
    fsl_size_t npos = sb->used + n;
    if( npos >= sb->capacity ){
      const size_t asz = npos ? ((4 * npos / 3) + 1) : 16;
      if( asz < npos ) {
        ba->rc = FSL_RC_RANGE;
        return -1; /* overflow */
      }
      else{
        rc = fsl_buffer_reserve( sb, asz );
        if(rc) {
          ba->rc = FSL_RC_OOM;
          return -1;
        }
      }
    }
    rc = 0;
    for( ; rc < n; ++rc, ++sb->used ){
      sb->mem[sb->used] = data[rc];
    }
    sb->mem[sb->used] = 0;
    return rc;
  }
}

int fsl_buffer_appendfv( fsl_buffer * b,
                         char const * fmt, va_list args){
  if(!b || !fmt) return FSL_RC_MISUSE;
  else{
    BufferAppender ba;
    ba.b = b;
    ba.rc = 0;
    fsl_appendfv( fsl_appendf_f_buffer, &ba, fmt, args );
    return ba.rc;
  }
}


int fsl_buffer_appendf( fsl_buffer * b,
                        char const * fmt, ... ){
  if(!b || !fmt) return FSL_RC_MISUSE;
  else{
    int rc;
    va_list args;
    va_start(args,fmt);
    rc = fsl_buffer_appendfv( b, fmt, args );
    va_end(args);
    return rc;
  }
}

char const * fsl_buffer_cstr(fsl_buffer const *b){
  return b ? (char const *)b->mem : NULL;
}

char const * fsl_buffer_cstr2(fsl_buffer const *b, fsl_size_t * len){
  char const * rc = NULL;
  if(b){
    rc = (char const *)b->mem;
    if(len) *len = b->used;
  }
  return rc;
}

char * fsl_buffer_str(fsl_buffer const *b){
  return b ? (char *)b->mem : NULL;
}


fsl_size_t fsl_buffer_size(fsl_buffer const * b){
  return b ? b->used : 0U;
}

fsl_size_t fsl_buffer_capacity(fsl_buffer const * b){
  return b ? b->capacity : 0;
}

bool fsl_data_is_compressed(unsigned char const * mem, fsl_size_t len){
  if(!mem || (len<6)) return 0;
#if 0
  else return ('x'==mem[4])
    && (0234==mem[5]);
  /*
    This check fails for one particular artifact in the tcl core.
    Notes gathered while debugging...

    https://core.tcl.tk/tcl/

    Delta manifest #5f37dcc3 while processing file #687
    (1-based):

    FSL_RC_RANGE: "Delta: copy extends past end of input"

    To reproduce from tcl repo:

    f-acat 5f37dcc3 | f-mfparse -r

    More details:

    Filename: library/encoding/gb2312-raw.enc
    Content: dba09c670f24d47b95d12d4bb9704391b81dda9a

    That artifact is a delta of bccc899015b688d5c426bc791c2fcde3a03a3eb5,
    which is actually two files:

    library/encoding/euc-cn.enc
    library/encoding/gb2312.enc

    When we go to apply the delta, the contents of bccc8 appear to
    be badly compressed data. They have the 'x' at byte offset
    4 but not the 0234 at byte offset 5.

    Turns out it is the fsl_buffer_is_compressed() impl which fails
    for that one.
  */
#else
  else{
    /**
       Adapted from:
       
       https://blog.2of1.org/2011/03/03/decompressing-zlib-images/

       Remember that fossil-compressed data has a 4-byte big-endian
       header holding the uncompressed size of the data, so we skip
       those first 4 bytes.

       See also:

       https://tools.ietf.org/html/rfc6713

       search for "magic number".
    */
    int16_t const head = (((int16_t)mem[4]) << 8) | mem[5];
    /* MARKER(("isCompressed header=%04x\n", head)); */
    switch(head){
      case 0x083c: case 0x087a: case 0x08b8: case 0x08f6:
      case 0x1838: case 0x1876: case 0x18b4: case 0x1872:
      case 0x2834: case 0x2872: case 0x28b0: case 0x28ee:
      case 0x3830: case 0x386e: case 0x38ac: case 0x38ea:
      case 0x482c: case 0x486a: case 0x48a8: case 0x48e6:
      case 0x5828: case 0x5866: case 0x58a4: case 0x58e2:
      case 0x6824: case 0x6862: case 0x68bf: case 0x68fd:
      case 0x7801: case 0x785e: case 0x789c: case 0x78da:
        return true;
      default:
        return false;
    }
  }
#endif
}

bool fsl_buffer_is_compressed(fsl_buffer const *buf){
  return fsl_data_is_compressed( buf->mem, buf->used );
}

fsl_int_t fsl_data_uncompressed_size(unsigned char const *mem,
                                     fsl_size_t len){
  return fsl_data_is_compressed(mem,len)
    ? ((mem[0]<<24) + (mem[1]<<16) + (mem[2]<<8) + mem[3])
    : -1;
}

fsl_int_t fsl_buffer_uncompressed_size(fsl_buffer const * b){
  return fsl_data_uncompressed_size(b->mem, b->used);
}

int fsl_buffer_compress(fsl_buffer const *pIn, fsl_buffer *pOut){
  unsigned int nIn = pIn->used;
  unsigned int nOut = 13 + nIn + (nIn+999)/1000;
  fsl_buffer temp = fsl_buffer_empty;
  int rc = fsl_buffer_resize(&temp, nOut+4);
  if(rc) return rc;
  else{
    unsigned long int nOut2;
    unsigned char *outBuf;
    unsigned long int outSize;
    outBuf = temp.mem;
    outBuf[0] = nIn>>24 & 0xff;
    outBuf[1] = nIn>>16 & 0xff;
    outBuf[2] = nIn>>8 & 0xff;
    outBuf[3] = nIn & 0xff;
    nOut2 = (long int)nOut;
    rc = compress(&outBuf[4], &nOut2,
                  pIn->mem, pIn->used);
    if(rc){
      fsl_buffer_clear(&temp);
      return FSL_RC_ERROR;
    }
    outSize = nOut2+4;
    rc = fsl_buffer_resize(&temp, outSize);
    if(rc){
      fsl_buffer_clear(&temp);
    }else{
      fsl_buffer_swap_free(&temp, pOut, -1);
      assert(0==temp.used);
      assert(outSize==pOut->used);
    }
    return rc;
  }
}

int fsl_buffer_compress2(fsl_buffer const *pIn1,
                         fsl_buffer const *pIn2, fsl_buffer *pOut){
  unsigned int nIn = pIn1->used + pIn2->used;
  unsigned int nOut = 13 + nIn + (nIn+999)/1000;
  fsl_buffer temp = fsl_buffer_empty;
  int rc;
  rc = fsl_buffer_resize(&temp, nOut+4);
  if(rc) return rc;
  else{
    unsigned char *outBuf;
    z_stream stream;
    outBuf = temp.mem;
    outBuf[0] = nIn>>24 & 0xff;
    outBuf[1] = nIn>>16 & 0xff;
    outBuf[2] = nIn>>8 & 0xff;
    outBuf[3] = nIn & 0xff;
    stream.zalloc = (alloc_func)0;
    stream.zfree = (free_func)0;
    stream.opaque = 0;
    stream.avail_out = nOut;
    stream.next_out = &outBuf[4];
    deflateInit(&stream, 9);
    stream.avail_in = pIn1->used;
    stream.next_in = pIn1->mem;
    deflate(&stream, 0);
    stream.avail_in = pIn2->used;
    stream.next_in = pIn2->mem;
    deflate(&stream, 0);
    deflate(&stream, Z_FINISH);
    rc = fsl_buffer_resize(&temp, stream.total_out + 4);
    deflateEnd(&stream);
    if(!rc){
      temp.used = stream.total_out + 4;
      if( pOut==pIn1 ) fsl_buffer_reserve(pOut, 0);
      else if( pOut==pIn2 ) fsl_buffer_reserve(pOut, 0);
      assert(!pOut->mem);
      *pOut = temp;
    }else{
      fsl_buffer_reserve(&temp, 0);
    }
    return rc;
  }
}

int fsl_buffer_uncompress(fsl_buffer const *pIn, fsl_buffer *pOut){
  unsigned int nOut;
  unsigned char *inBuf;
  unsigned int nIn = pIn->used;
  fsl_buffer temp = fsl_buffer_empty;
  int rc;
  unsigned long int nOut2;
  if( nIn<=4 ){
    return FSL_RC_RANGE;
  }
  inBuf = pIn->mem;
  nOut = (inBuf[0]<<24) + (inBuf[1]<<16) + (inBuf[2]<<8) + inBuf[3];
  /* MARKER(("decompress size: %u\n", nOut)); */
  rc = fsl_buffer_reserve(&temp, nOut+1);
  if(rc) return rc;
  nOut2 = (long int)nOut;
  rc = uncompress(temp.mem, &nOut2,
                  &inBuf[4], nIn - 4)
    /* valgrind says there's an uninitialized memory access
       somewhere under uncompress(), _presumably_ for one of
       these arguments, but i can't find it. fsl_buffer_reserve()
       always memsets() new bytes to 0.

       Turns out it's a known problem:

       https://www.zlib.net/zlib_faq.html#faq36
    */;
  if( rc!=Z_OK ){
    fsl_buffer_reserve(&temp, 0);
    return FSL_RC_ERROR;
  }
  rc = fsl_buffer_resize(&temp, nOut2);
  if(!rc){
    temp.used = (fsl_size_t)nOut2;
    if( pOut==pIn ){
      fsl_buffer_reserve(pOut, 0);
    }
    assert(!pOut->mem);
    *pOut = temp;
  }else{
    fsl_buffer_reserve(&temp, 0);
  }
  return rc;
}


int fsl_buffer_fill_from( fsl_buffer * dest, fsl_input_f src, void * state )
{
  int rc;
  enum { BufSize = 512 * 8 };
  char rbuf[BufSize];
  fsl_size_t total = 0;
  fsl_size_t rlen = 0;
  if( !dest || ! src ) return FSL_RC_MISUSE;
  fsl_buffer_reuse(dest);
  while(1){
    rlen = BufSize;
    rc = src( state, rbuf, &rlen );
    if( rc ) break;
    total += rlen;
    if(total<rlen){
      /* Overflow! */
      rc = FSL_RC_RANGE;
      break;
    }
    if( dest->capacity < (total+1) ){
      rc = fsl_buffer_reserve( dest,
                               total + ((rlen<BufSize) ? 1 : BufSize)
                               );
      if( 0 != rc ) break;
    }
    memcpy( dest->mem + dest->used, rbuf, rlen );
    dest->used += rlen;
    if( rlen < BufSize ) break;
  }
  if( !rc && dest->used ){
    assert( dest->used < dest->capacity );
    dest->mem[dest->used] = 0;
  }
  return rc;
}

int fsl_buffer_fill_from_FILE( fsl_buffer * dest, FILE * src ){
  return (!dest || !src)
          ? FSL_RC_MISUSE
          : fsl_buffer_fill_from( dest, fsl_input_f_FILE, src );
}          


int fsl_buffer_fill_from_filename( fsl_buffer * dest, char const * filename ){
  if(!dest || !filename || !*filename) return FSL_RC_MISUSE;
  else{
    int rc;
    FILE * src;
    fsl_fstat st = fsl_fstat_empty;
    /* This stat() is only an optimization to reserve all needed
       memory up front.
    */
    rc = fsl_stat( filename, &st, 1 );
    if(!rc && st.size>0){
      rc = fsl_buffer_reserve(dest, st.size +1/*NUL terminator*/);
      if(rc) return rc;
    } /* Else it might not be a real file, e.g. "-", so we'll try anyway... */
    src = fsl_fopen(filename,"rb");
    if(!src) rc = fsl_errno_to_rc(errno, FSL_RC_IO);
    else {
      rc = fsl_buffer_fill_from( dest, fsl_input_f_FILE, src );
      fsl_fclose(src);
    }
    return rc;
  }
}

void fsl_buffer_swap( fsl_buffer * left, fsl_buffer * right ){
  fsl_buffer const tmp = *left;
  *left = *right;
  *right = tmp;
}

void fsl_buffer_swap_free( fsl_buffer * left, fsl_buffer * right, int clearWhich ){
  fsl_buffer_swap(left, right);
  if(0 != clearWhich) fsl_buffer_reserve((clearWhich<0) ? left : right, 0);
}

int fsl_buffer_copy( fsl_buffer const * src, fsl_buffer * dest ){
  fsl_buffer_reuse(dest);
  return src->used
    ? fsl_buffer_append( dest, src->mem, src->used )
    : 0;
}

int fsl_buffer_delta_apply2( fsl_buffer const * orig,
                             fsl_buffer const * pDelta,
                             fsl_buffer * pTarget,
                             fsl_error * pErr){
  int rc;
  fsl_size_t n = 0;
  fsl_buffer out = fsl_buffer_empty;
  rc = fsl_delta_applied_size( pDelta->mem, pDelta->used, &n);
  if(rc){
    if(pErr){
      fsl_error_set(pErr, rc, "fsl_delta_applied_size() failed.");
    }
    return rc;
  }
  rc = fsl_buffer_resize( &out, n );
  if(rc) return rc;
  rc = fsl_delta_apply2( orig->mem, orig->used,
                        pDelta->mem, pDelta->used,
                        out.mem, pErr);
  if(rc){
    fsl_buffer_clear(&out);
  }else{
    fsl_buffer_clear(pTarget);
    *pTarget = out;
  }
  return rc;
}

int fsl_buffer_delta_apply( fsl_buffer const * orig,
                            fsl_buffer const * pDelta,
                            fsl_buffer * pTarget){
  return fsl_buffer_delta_apply2(orig, pDelta, pTarget, NULL);
}

void fsl_buffer_defossilize( fsl_buffer * b ){
  if(b){
    fsl_bytes_defossilize( b->mem, &b->used );
  }
}

int fsl_buffer_to_filename( fsl_buffer const * b, char const * fname ){
  FILE * f;
  int rc = 0;
  if(!b || !fname) return FSL_RC_MISUSE;
  f = fsl_fopen(fname, "wb");
  if(!f) rc = fsl_errno_to_rc(errno, FSL_RC_IO);
  else{
    if(b->used) {
      size_t const frc = fwrite(b->mem, b->used, 1, f);
      rc = (1==frc) ? 0 : FSL_RC_IO;
    }
    fsl_fclose(f);
  }
  return rc;
}

int fsl_buffer_delta_create( fsl_buffer const * src,
                             fsl_buffer const * newVers,
                             fsl_buffer * delta){
  if(!src || !newVers || !delta) return FSL_RC_MISUSE;
  else if((src == newVers)
          || (src==delta)
          || (newVers==delta)) return FSL_RC_MISUSE;
  else{
    int rc = fsl_buffer_reserve( delta, newVers->used + 60 );
    if(!rc){
      delta->used = 0;
      rc = fsl_delta_create( src->mem, src->used,
                             newVers->mem, newVers->used,
                             delta->mem, &delta->used );
      if(!rc){
        rc = fsl_buffer_resize( delta, delta->used );
      }
    }
    return rc;
  }
}


int fsl_output_f_buffer( void * state,
                         void const * src, fsl_size_t n ){
  return (!state || !src)
    ? FSL_RC_MISUSE
    : fsl_buffer_append((fsl_buffer*)state, src, n);
}

int fsl_finalizer_f_buffer( void * state, void * mem ){
  fsl_buffer * b = (fsl_buffer*)mem;
  fsl_buffer_reserve(b, 0);
  *b = fsl_buffer_empty;
  return 0;
}

int fsl_buffer_strftime(fsl_buffer * b, char const * format, const struct tm *timeptr){
  if(!b || !format || !*format || !timeptr) return FSL_RC_MISUSE;
  else{
    enum {BufSize = 128};
    char buf[BufSize];
    fsl_size_t len = fsl_strftime(buf, BufSize, format, timeptr);
    if(!len) return FSL_RC_RANGE;
    return fsl_buffer_append(b, buf, len);
  }
}

int fsl_buffer_stream_lines(fsl_output_f fTo, void * toState,
                            fsl_buffer *pFrom, fsl_size_t N){
  char *z = (char *)pFrom->mem;
  fsl_size_t i = pFrom->cursor;
  fsl_size_t n = pFrom->used;
  fsl_size_t cnt = 0;
  int rc = 0;
  if( N==0 ) return 0;
  while( i<n ){
    if( z[i]=='\n' ){
      cnt++;
      if( cnt==N ){
        i++;
        break;
      }
    }
    i++;
  }
  if( fTo ){
    rc = fTo(toState, &pFrom->mem[pFrom->cursor], i - pFrom->cursor);
  }
  if(!rc){
    pFrom->cursor = i;
  }
  return rc;
}


int fsl_buffer_copy_lines(fsl_buffer *pTo, fsl_buffer *pFrom, fsl_size_t N){
#if 1
  return fsl_buffer_stream_lines( pTo ? fsl_output_f_buffer : NULL, pTo,
                                  pFrom, N );
#else
  char *z = (char *)pFrom->mem;
  fsl_size_t i = pFrom->cursor;
  fsl_size_t n = pFrom->used;
  fsl_size_t cnt = 0;
  int rc = 0;
  if( N==0 ) return 0;
  while( i<n ){
    if( z[i]=='\n' ){
      ++cnt;
      if( cnt==N ){
        ++i;
        break;
      }
    }
    ++i;
  }
  if( pTo ){
    rc = fsl_buffer_append(pTo, &pFrom->mem[pFrom->cursor], i - pFrom->cursor);
  }
  if(!rc){
    pFrom->cursor = i;
  }
  return rc;
#endif
}

int fsl_input_f_buffer( void * state, void * dest, fsl_size_t * n ){
  fsl_buffer * b = (fsl_buffer*)state;
  fsl_size_t const from = b->cursor;
  fsl_size_t to;
  fsl_size_t c;
  if(from >= b->used){
    *n = 0;
    return 0;
  }
  to = from + *n;
  if(to>b->used) to = b->used;
  c = to - from;
  if(c){
    memcpy(dest, b->mem+from, c);
    b->cursor += c;
  }
  *n = c;
  return 0;
}

int fsl_buffer_compare_file( fsl_buffer const * b, char const * zFile ){
  int rc;
  fsl_fstat fst = fsl_fstat_empty;
  rc = fsl_stat(zFile, &fst, 1);
  if(rc || (FSL_FSTAT_TYPE_FILE != fst.type)) return -1;
  else if(b->used < fst.size) return -1;
  else if(b->used > fst.size) return 1;
  else{
#if 1
    FILE * f;
    f = fsl_fopen(zFile,"r");
    if(!f) rc = -1;
    else{
      fsl_buffer fc = *b /* so fsl_input_f_buffer() can manipulate its
                            cursor */;
      rc = fsl_stream_compare(fsl_input_f_buffer, &fc,
                              fsl_input_f_FILE, f);
      assert(fc.mem==b->mem);
      fsl_fclose(f);
    }

#else
    fsl_buffer fc = fsl_buffer_empty;
    rc = fsl_buffer_fill_from_filename(&fc, zFile);
    if(rc){
      rc = -1;
    }else{
      rc = fsl_buffer_compare(b, &fc);
    }
    fsl_buffer_clear(&fc);
#endif
    return rc;
  }
}

char * fsl_buffer_take(fsl_buffer *b){
  char * z = (char *)b->mem;
  *b = fsl_buffer_empty;
  return z;
}

fsl_size_t fsl_buffer_seek(fsl_buffer * b, fsl_int_t offset, fsl_buffer_seek_e  whence){
  int64_t c = (int64_t)b->cursor;
  switch(whence){
    case FSL_BUFFER_SEEK_SET: c = offset;
    case FSL_BUFFER_SEEK_CUR: c = (int64_t)b->cursor + offset; break;
    case FSL_BUFFER_SEEK_END:
      c = (int64_t)b->used + offset;
      /* ^^^^^ fossil(1) uses (used + offset - 1) but

         That seems somewhat arguable because (used + 0 - 1) is at the
         last-written byte (or 1 before the begining), not the
         one-past-the-end point (which corresponds to the
         "end-of-file" described by the fseek() man page). It then
         goes on, in other algos, to operate on that final byte using
         that position, e.g.  blob_read() after a seek-to-end would
         read that last byte, rather than treating the buffer as being
         at the end.

         So... i'm going to naively remove that -1 bit.
      */
      break;
  }
  if(!b->used || c<0) b->cursor = 0;
  else if((fsl_size_t)c > b->used) b->cursor = b->used;
  else b->cursor = (fsl_size_t)c;
  return b->cursor;
}

fsl_size_t fsl_buffer_tell(fsl_buffer const *b){
  return b->cursor;
}

void fsl_buffer_rewind(fsl_buffer *b){
  b->cursor = 0;
}

int fsl_id_bag_to_buffer(fsl_id_bag const * bag, fsl_buffer * b,
                         char const * separator){
  int i = 0;
  fsl_int_t const sepLen = (fsl_id_t)fsl_strlen(separator);
  int rc = fsl_buffer_reserve(b, b->used + (bag->entryCount * 7)
                              + (bag->entryCount * sepLen));
  for(fsl_id_t e = fsl_id_bag_first(bag);
      !rc && e; e = fsl_id_bag_next(bag, e)){
    if(i++) rc = fsl_buffer_append(b, separator, sepLen);
    if(!rc) rc = fsl_buffer_appendf(b, "%" FSL_ID_T_PFMT, e);
  }
  return rc;
}

#undef MARKER
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<








































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/cache.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 some of the caching-related APIs.
*/
#include "fossil-scm/fossil-internal.h"
#include <assert.h>

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

bool fsl_acache_expire_oldest(fsl_acache * c){
  static uint16_t const sentinel = 0xFFFF;
  uint16_t i;
  fsl_int_t mnAge = c->nextAge;
  uint16_t mn = sentinel;
  for(i=0; i<c->used; i++){
    if( c->list[i].age<mnAge ){
      mnAge = c->list[i].age;
      mn = i;
    }
  }
  if( mn<sentinel ){
    fsl_id_bag_remove(&c->inCache, c->list[mn].rid);
    c->szTotal -= (uint32_t)c->list[mn].content.capacity;
    fsl_buffer_clear(&c->list[mn].content);
    --c->used;
    c->list[mn] = c->list[c->used];
  }
  return sentinel!=mn;
}

int fsl_acache_insert(fsl_acache * c, fsl_id_t rid, fsl_buffer *pBlob){
  fsl_acache_line *p;
  if( c->used>c->usedLimit || c->szTotal>c->szLimit ){
    fsl_size_t szBefore;
    do{
      szBefore = c->szTotal;
      fsl_acache_expire_oldest(c);
    }while( c->szTotal>c->szLimit && c->szTotal<szBefore );
  }
  if((!c->usedLimit || !c->szLimit)
     || (c->used+1 >= c->usedLimit)){
    fsl_buffer_clear(pBlob);
    return 0;
  }
  if( c->used>=c->capacity ){
    uint16_t const cap = c->capacity ? (c->capacity*2) : 10;
    void * remem = c->list
      ? fsl_realloc(c->list, cap*sizeof(c->list[0]))
      : fsl_malloc( cap*sizeof(c->list[0]) );
    assert((c->capacity && cap<c->capacity) ? !"Numeric overflow" : 1);
    if(c->capacity && cap<c->capacity){
        fsl_fatal(FSL_RC_RANGE,"Numeric overflow. Bump "
                  "fsl_acache::capacity to a larger int type.");
    }
    if(!remem){
      fsl_buffer_clear(pBlob) /* for consistency */;
      return FSL_RC_OOM;
    }
    c->capacity = cap;
    c->list = (fsl_acache_line*)remem;
  }
  p = &c->list[c->used++];
  p->rid = rid;
  p->age = c->nextAge++;
  c->szTotal += pBlob->capacity;
  p->content = *pBlob /* Transfer ownership */;
  *pBlob = fsl_buffer_empty;
  return fsl_id_bag_insert(&c->inCache, rid);
}


void fsl_acache_clear(fsl_acache * c){
#if 0
  while(fsl_acache_expire_oldest(c)){}
#else
  fsl_size_t i;
  for(i=0; i<c->used; i++){
    fsl_buffer_clear(&c->list[i].content);
  }
#endif
  fsl_free(c->list);
  fsl_id_bag_clear(&c->missing);
  fsl_id_bag_clear(&c->available);
  fsl_id_bag_clear(&c->inCache);
  *c = fsl_acache_empty;
}


int fsl_acache_check_available(fsl_cx * f, fsl_id_t rid){
  fsl_id_t srcid;
  int depth = 0;  /* Limit to recursion depth */
  static const int limit = 10000000 /* historical value */;
  int rc;
  fsl_acache * c = &f->cache.arty;
  assert(f);
  assert(c);
  assert(rid>0);
  assert(fsl_cx_db_repo(f));
  while( depth++ < limit ){  
    fsl_int_t cSize = -1;
    if( fsl_id_bag_contains(&c->missing, rid) ){
      return FSL_RC_NOT_FOUND;
    }
    else if( fsl_id_bag_contains(&c->available, rid) ){
      return 0;
    }
    else if( (cSize=fsl_content_size(f, rid)) <0){
      rc = fsl_id_bag_insert(&c->missing, rid);
      return rc ? rc : FSL_RC_NOT_FOUND;
    }
    srcid = 0;
    rc = fsl_delta_src_id(f, rid, &srcid);
    if(rc) return rc;
    else if( srcid==0 ){
      rc = fsl_id_bag_insert(&c->available, rid);
      return rc ? rc : 0;
    }
    rid = srcid;
  }
  assert(!"delta-loop in repository");
  return fsl_cx_err_set(f, FSL_RC_CONSISTENCY,
                        "Serious problem: delta-loop in repository");
}

#undef MARKER
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


























































































































































































































































































Deleted src/checkin.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 houses the code for checkin-level APIS.
*/
#include <assert.h>

#include "fossil-scm/fossil-internal.h"
#include "fossil-scm/fossil-checkout.h"
#include "fossil-scm/fossil-confdb.h"

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


/**
   Expects f to have an opened checkout. Assumes zRelName is a
   checkout-relative simple path. It loads the file's contents and
   stores them into the blob table. If rid is not NULL, *rid is
   assigned the blob.rid (possibly new, possilbly re-used!). If uuid
   is not NULL then *uuid is assigned to the content's UUID. The *uuid
   bytes are owned by the caller, who must eventually fsl_free()
   them. If content with the same UUID already exists, it does not get
   re-imported but rid/uuid will (if not NULL) contain the old values.

   If parentRid is >0 then it must refer to the previous version of
   zRelName's content. The parent version gets deltified vs the new
   one. Note that deltification is a suggestion which the library will
   ignore if (e.g.) the parent content is already a delta of something
   else.

   The wise caller will have a transaction in place when calling this.

   Returns 0 on success. On error rid and uuid are not modified.
*/
static int fsl_checkin_import_file( fsl_cx * f, char const * zRelName,
                                    fsl_id_t parentRid,
                                    bool allowMergeConflict,
                                    fsl_id_t *rid, fsl_uuid_str * uuid){
  fsl_buffer * nbuf = fsl_cx_scratchpad(f);
  fsl_size_t const oldSize = nbuf->used;
  fsl_buffer * fbuf = &f->fileContent;
  char const * fn;
  int rc;
  fsl_id_t fnid = 0;
  fsl_id_t rcRid = 0;
  assert(!fbuf->used && "Misuse of f->fileContent");
  assert(f->ckout.dir);
  rc = fsl_repo_filename_fnid2(f, zRelName, &fnid, 1);
  if(rc) goto end;
  assert(fnid>0);

  rc = fsl_buffer_appendf(nbuf, "%s%s", f->ckout.dir, zRelName);
  nbuf->used = oldSize;
  if(rc) goto end;
  fn = fsl_buffer_cstr(nbuf) + oldSize;
  rc = fsl_buffer_fill_from_filename( fbuf, fn );
  if(rc){
    fsl_cx_err_set(f, rc, "Error %s importing file: %s",
                   fsl_rc_cstr(rc), fn);
    goto end;
  }else if(!allowMergeConflict &&
           fsl_buffer_contains_merge_marker(fbuf)){
    rc = fsl_cx_err_set(f, FSL_RC_CONFLICT,
                        "File contains a merge conflict marker: %s",
                        zRelName);
    goto end;
  }

  rc = fsl_content_put( f, fbuf, &rcRid );
  if(!rc){
    assert(rcRid > 0);
    if(parentRid>0){
      rc = fsl_content_deltify(f, parentRid, rcRid, 0);
    }
    if(!rc){
      if(rid) *rid = rcRid;
      if(uuid){
        *uuid = fsl_rid_to_uuid(f, rcRid);
        if(!*uuid) rc = (f->error.code ? f->error.code : FSL_RC_OOM);
      }
    }
  }
  end:
  fsl_cx_scratchpad_yield(f, nbuf);
  fsl_cx_content_buffer_yield(f);
  assert(0==fbuf->used);
  return rc;
}

int fsl_filename_to_vfile_ids( fsl_cx * f, fsl_id_t vid,
                               fsl_id_bag * dest, char const * zName,
                               bool changedOnly){
  fsl_stmt st = fsl_stmt_empty;
  fsl_db * const db = fsl_needs_ckout(f);
  int rc;
  fsl_buffer * sql = 0;
  if(!db) return FSL_RC_NOT_A_CKOUT;
  sql = fsl_cx_scratchpad(f);
  if(0>=vid) vid = f->ckout.rid;
  if(zName && *zName
     && !('.'==*zName && !zName[1])){
    rc = fsl_buffer_appendf(sql,
                            "SELECT id FROM vfile WHERE vid=%"
                            FSL_ID_T_PFMT
                            " AND fsl_match_vfile_or_dir(pathname,%Q)",
                            vid, zName);
  }else{
    rc = fsl_buffer_appendf(sql,
                            "SELECT id FROM vfile WHERE vid=%" FSL_ID_T_PFMT,
                            vid);
  }
  if(rc) goto end;
  else if(changedOnly){
    rc = fsl_buffer_append(sql, " AND (chnged OR deleted OR rid=0 "
                           "OR (origname IS NOT NULL AND "
                           "    origname<>pathname))", -1);
    if(rc) goto end;
  }
  rc = fsl_buffer_appendf(sql, " /* %s() */", __func__);
  if(rc) goto end;
  rc = fsl_db_prepare(db, &st, "%b", sql);
  while(!rc && (FSL_RC_STEP_ROW == (rc=fsl_stmt_step(&st)))){
    rc = fsl_id_bag_insert( dest, fsl_stmt_g_id(&st, 0) );
  }
  if(FSL_RC_STEP_DONE==rc) rc = 0;
  end:
  fsl_cx_scratchpad_yield(f, sql);
  fsl_stmt_finalize(&st);
  if(rc && !f->error.code && db->error.code){
    fsl_cx_uplift_db_error(f, db);
  }
  return rc;
}

int fsl_filename_to_vfile_id( fsl_cx * f, fsl_id_t vid, char const * zName, fsl_id_t * vfid ){
  fsl_db * db = fsl_needs_ckout(f);
  int rc;
  fsl_stmt st = fsl_stmt_empty;
  assert(db);
  if(!db) return FSL_RC_NOT_A_CKOUT;
  else if(!zName || !fsl_is_simple_pathname(zName, true)){
    return fsl_cx_err_set(f, FSL_RC_RANGE,
                          "Filename is not a \"simple\" path: %s",
                          zName);
  }
  if(0>=vid) vid = f->ckout.rid;
  rc = fsl_db_prepare(db, &st,
                      "SELECT id FROM vfile WHERE vid=%" FSL_ID_T_PFMT
                      " AND pathname=%Q %s /*%s()*/",
                      vid, zName, 
                      fsl_cx_filename_collation(f),
                      __func__);
  if(!rc){
    rc = fsl_stmt_step(&st);
    switch(rc){
      case FSL_RC_STEP_ROW:
        rc = 0;
        *vfid = fsl_stmt_g_id(&st, 0);
        break;
      case FSL_RC_STEP_DONE:
        rc = 0;
        /* fall through */
      default:
        *vfid = 0;
    }
    fsl_stmt_finalize(&st);
  }
  if(rc){
    rc = fsl_cx_uplift_db_error2(f, db, rc);
  }
  return rc;
}

/**
   Internal helper for fsl_checkin_enqueue() and
   fsl_checkin_dequeue(). Prepares, if needed, st with a query to
   fetch a vfile entry where vfile.id=vfid, then passes that name on
   to opt->callback(). Returns 0 on success.
*/
static int fsl_xqueue_callback(fsl_cx * f, fsl_db * db, fsl_stmt * st,
                               fsl_id_t vfid,
                               fsl_checkin_queue_opt const * opt){

  int rc;
  assert(opt->callback);
  if(!st->stmt){
    rc = fsl_db_prepare(db, st,
                        "SELECT pathname FROM vfile "
                        "WHERE id=?1");
    if(rc) return fsl_cx_uplift_db_error2(f, db, rc);
  }
  fsl_stmt_bind_id(st, 1, vfid);
  rc = fsl_stmt_step(st);
  switch(rc){
    case FSL_RC_STEP_ROW:{
      char const * zName = fsl_stmt_g_text(st, 0, NULL);
      rc = opt->callback(zName, opt->callbackState);
      break;
    }
    case FSL_RC_STEP_DONE:
      rc = fsl_cx_err_set(f, rc, "Very unexpectedly did not find "
                          "vfile.id which we just found.");
      break;
    default:
      rc = fsl_cx_uplift_db_error2(f, db, rc);
      break;
  }
  fsl_stmt_reset(st);
  return rc;
}

int fsl_checkin_enqueue(fsl_cx * f, fsl_checkin_queue_opt const * opt){
  fsl_db * const db = fsl_needs_ckout(f);
  if(!db) return FSL_RC_NOT_A_CKOUT;
  fsl_buffer * const canon = opt->vfileIds ? 0 : fsl_cx_scratchpad(f);
  fsl_stmt qName = fsl_stmt_empty;
  fsl_id_bag _vfileIds = fsl_id_bag_empty;
  fsl_id_bag const * const vfileIds =
    opt->vfileIds ? opt->vfileIds : &_vfileIds;
  int rc = fsl_db_transaction_begin(db);
  if(rc) return fsl_cx_uplift_db_error2(f, db, rc);
  if(opt->vfileIds){
    if(!fsl_id_bag_count(opt->vfileIds)){
      rc = fsl_cx_err_set(f, FSL_RC_MISUSE,
                          "fsl_checkin_queue_opt::vfileIds "
                          "may not be empty.");
      goto end;
    }
  }else{
    rc = fsl_ckout_filename_check(f, opt->relativeToCwd,
                                  opt->filename, canon);
    if(rc) goto end;
    fsl_buffer_strip_slashes(canon);
  }
  if(opt->scanForChanges){
    rc = fsl_vfile_changes_scan(f, -1, 0);
    if(rc) goto end;
  }
  if(opt->vfileIds){
    assert(vfileIds == opt->vfileIds);
  }else{
    assert(vfileIds == &_vfileIds);
    rc = fsl_filename_to_vfile_ids(f, 0, &_vfileIds,
                                   fsl_buffer_cstr(canon),
                                   opt->onlyModifiedFiles);
  }
  if(rc) goto end;
  /* Walk through each found ID and queue up any which are not already
     enqueued. */
  for(fsl_id_t vfid = fsl_id_bag_first(vfileIds);
      !rc && vfid; vfid = fsl_id_bag_next(vfileIds, vfid)){
    fsl_size_t const entryCount = f->ckin.selectedIds.entryCount;
    rc = fsl_id_bag_insert(&f->ckin.selectedIds, vfid);
    if(!rc
       && entryCount < f->ckin.selectedIds.entryCount
       /* Was enqueued */
       && opt->callback){
      rc = fsl_xqueue_callback(f, db, &qName, vfid, opt);
    }
  }
  end:
  if(opt->vfileIds){
    assert(!canon);
    assert(!_vfileIds.list);
  }else{
    assert(canon);
    fsl_cx_scratchpad_yield(f, canon);
    fsl_id_bag_clear(&_vfileIds);
  }
  fsl_stmt_finalize(&qName);
  if(rc) fsl_db_transaction_rollback(db);
  else{
    rc = fsl_cx_uplift_db_error2(f, db, fsl_db_transaction_commit(db));
  }
  return rc;
}

int fsl_checkin_dequeue(fsl_cx * f, fsl_checkin_queue_opt const * opt){
  fsl_db * const db = fsl_needs_ckout(f);
  if(!db) return FSL_RC_NOT_A_CKOUT;
  int rc = fsl_db_transaction_begin(db);
  if(rc) return fsl_cx_uplift_db_error2(f, db, rc);
  fsl_id_bag list = fsl_id_bag_empty;
  fsl_buffer * canon = 0;
  char const * fn;
  fsl_stmt qName = fsl_stmt_empty;
  if(opt->filename && *opt->filename){
    canon = fsl_cx_scratchpad(f);
    rc = fsl_ckout_filename_check(f, opt->relativeToCwd,
                                  opt->filename, canon);
    if(rc) goto end;
    else fsl_buffer_strip_slashes(canon);
  }
  fn = canon ? fsl_buffer_cstr(canon) : opt->filename;
  rc = fsl_filename_to_vfile_ids(f, 0, &list, fn, false);
  if(!rc && list.entryCount){
    /* Walk through each found ID and dequeue up any which are
       enqueued. */
    for( fsl_id_t nid = fsl_id_bag_first(&list);
         !rc && nid;
         nid = fsl_id_bag_next(&list, nid)){
      if(fsl_id_bag_remove(&f->ckin.selectedIds, nid)
         && opt->callback){
        rc = fsl_xqueue_callback(f, db, &qName, nid, opt);
      }
    }
  }
  end:
  if(canon) fsl_cx_scratchpad_yield(f, canon);
  fsl_stmt_finalize(&qName);
  fsl_id_bag_clear(&list);
  if(rc) fsl_db_transaction_rollback(db);
  else{
    rc = fsl_cx_uplift_db_error2(f, db, fsl_db_transaction_commit(db));
  }
  return rc;
}

bool fsl_checkin_is_enqueued(fsl_cx * f, char const * zName,
                             bool relativeToCwd){
  fsl_db * db;
  if(!f || !zName || !*zName) return 0;
  else if(!(db = fsl_needs_ckout(f))) return 0;
  else if(!f->ckin.selectedIds.entryCount){
    /* Behave like fsl_is_enqueued() SQL function. */
    return true;
  }
  else {
    bool rv = false;
    fsl_buffer * const canon = fsl_cx_scratchpad(f);
    int rc = fsl_ckout_filename_check(f, relativeToCwd, zName, canon);
    if(!rc){
      fsl_id_t vfid = 0;
      rc = fsl_filename_to_vfile_id(f, 0, fsl_buffer_cstr(canon),
                                    &vfid);
      rv = (rc && (vfid>0))
        ? false
        : ((vfid>0)
           ? fsl_id_bag_contains(&f->ckin.selectedIds, vfid)
           /* ^^^^ asserts that arg2!=0*/
           : false);
    }
    fsl_cx_scratchpad_yield(f, canon);
    return rv;
  }
}


void fsl_checkin_discard(fsl_cx * f){
  if(f){
    fsl_id_bag_clear(&f->ckin.selectedIds);
    fsl_deck_finalize(&f->ckin.mf);
  }
}

/**
   Adds the given rid to the "unsent" db list, Returns 0 on success,
   updates f's error state on error.
*/
static int fsl_checkin_add_unsent(fsl_cx * f, fsl_id_t rid){
  fsl_db * const r = fsl_cx_db_repo(f);
  int rc;
  assert(r);
  rc = fsl_db_exec(r,"INSERT OR IGNORE INTO unsent "
                   "VALUES(%" FSL_ID_T_PFMT ")", rid);
  if(rc){
    fsl_cx_uplift_db_error(f, r);
  }
  return rc;
}

/**
   Calculates the F-cards for deck d based on the commit file
   selection list and the contents of the vfile table (where vid==the
   vid parameter). vid is the version to check against, and this code
   assumes that the vfile table has been populated with that version
   and its state represents a recent scan (with no filesystem-level
   changes made since the scan).

   If pBaseline is not NULL then d is calculated as being a delta
   from pBaseline, but d->B is not modified by this routine.

   On success, d->F.list will contain "all the F-cards it needs."

   If changeCount is not NULL, then on success it is set to the number
   of F-cards added to d due to changes queued via the checkin process
   (as opposed to those added solely for delta inheritance reasons).
*/
static
int fsl_checkin_calc_F_cards2( fsl_cx * f, fsl_deck * d,
                               fsl_deck * pBaseline, fsl_id_t vid,
                               fsl_size_t * changeCount,
                               fsl_checkin_opt const * ciOpt){
  int rc = 0;
  fsl_db * dbR = fsl_needs_repo(f);
  fsl_db * dbC = fsl_needs_ckout(f);
  fsl_stmt stUpdateFileRid = fsl_stmt_empty;
  fsl_stmt stmt = fsl_stmt_empty;
  fsl_stmt * q = &stmt;
  char * fUuid = NULL;
  fsl_card_F const * pFile = NULL;
  fsl_size_t changeCounter = 0;
  if(!f) return FSL_RC_MISUSE;
  else if(!dbR) return FSL_RC_NOT_A_REPO;
  else if(!dbC) return FSL_RC_NOT_A_CKOUT;
  assert( (!pBaseline || !pBaseline->B.uuid) && "Baselines must not have a baseline." );
  assert( d->B.baseline ? (!pBaseline || pBaseline==d->B.baseline) : 1 );
  assert(vid>=0);
#define RC if(rc) goto end

  if(pBaseline){
    assert(!d->B.baseline);
    assert(0!=vid);
    rc = fsl_deck_F_rewind(pBaseline);
    RC;
    fsl_deck_F_next( pBaseline, &pFile );
  }

  rc = fsl_db_prepare(dbC, &stUpdateFileRid,
                      "UPDATE vfile SET mrid=?1, rid=?1, "
                      "mhash=NULL WHERE id=?2");
  RC;

  rc = fsl_db_prepare( dbC, q,
                       "SELECT "
                       /*0*/"fsl_is_enqueued(vf.id) as isSel, "
                       /*1*/"vf.id,"
                       /*2*/"vf.vid,"
                       /*3*/"vf.chnged,"
                       /*4*/"vf.deleted,"
                       /*5*/"vf.isexe,"
                       /*6*/"vf.islink,"
                       /*7*/"vf.rid,"
                       /*8*/"mrid,"
                       /*9*/"pathname,"
                       /*10*/"origname, "
                       /*11*/"b.rid, "
                       /*12*/"b.uuid "
                       "FROM vfile vf LEFT JOIN blob b ON vf.mrid=b.rid "
                       "WHERE"
                       " vf.vid=%"FSL_ID_T_PFMT" AND"
#if 0
                       /* Historical (fossil(1)). This introduces an interesting
                          corner case which i would like to avoid here because
                          it causes a "no files changed" error in the checkin
                          op. The behaviour is actually correct (and the deletion
                          is picked up) but fsl_checkin_commit() has no mechanism
                          for catching this particular case. So we'll try a
                          slightly different approach...
                       */
                       " (NOT deleted OR NOT isSel)"
#else
                       " ((NOT deleted OR NOT isSel)"
                       "  OR (deleted AND isSel))" /* workaround to allow
                                                     us to count deletions via
                                                     changeCounter. */
#endif
                       " ORDER BY fsl_if_enqueued(vf.id, pathname, origname)",
                       (fsl_id_t)vid);
  RC;
  /* MARKER(("SQL:\n%s\n", (char const *)q->sql.mem)); */
  while( FSL_RC_STEP_ROW==fsl_stmt_step(q) ){
    int const isSel = fsl_stmt_g_int32(q,0);
    fsl_id_t const id = fsl_stmt_g_id(q,1);
#if 0
    fsl_id_t const vid = fsl_stmt_g_id(q,2);
#endif
    int const changed = fsl_stmt_g_int32(q,3);
    int const deleted = fsl_stmt_g_int32(q,4);
    int const isExe = fsl_stmt_g_int32(q,5);
    int const isLink = fsl_stmt_g_int32(q,6);
    fsl_id_t const rid = fsl_stmt_g_id(q,7);
    fsl_id_t const mergeRid = fsl_stmt_g_id(q,8);
    char const * zName = fsl_stmt_g_text(q, 9, NULL);
    char const * zOrig = fsl_stmt_g_text(q, 10, NULL);
    fsl_id_t const frid = fsl_stmt_g_id(q,11);
    char const * zUuid = fsl_stmt_g_text(q, 12, NULL);
    fsl_fileperm_e perm = FSL_FILE_PERM_REGULAR;
    int cmp;
    fsl_id_t fileBlobRid = rid;
    int const renamed = (zOrig && *zOrig) ? fsl_strcmp(zName,zOrig) : 0
      /* For some as-yet-unknown reason, some fossil(1) code
         sets (origname=pathname WHERE origname=NULL). e.g.
         the 'mv' command does that.
      */;
    if(zOrig && !renamed) zOrig = NULL;
    fUuid = NULL;
    if(!isSel && !zUuid){
      assert(!rid);
      assert(!mergeRid);
      /* An unselected ADDed file. Skip it. */
      continue;
    }

    if(isExe) perm = FSL_FILE_PERM_EXE;
    else if(isLink){
      fsl_fatal(FSL_RC_NYI, "This code does not yet deal "
                "with symlinks. file: %s", zName)
        /* does not return */;
      perm = FSL_FILE_PERM_LINK;
    }
    /*
      TODO: symlinks
    */

    if(!f->cache.markPrivate){
      rc = fsl_content_make_public(f, frid);
      if(rc) break;
    }

#if 0
    if(mergeRid && (mergeRid != rid)){
      fsl_fatal(FSL_RC_NYI, "This code does not yet deal "
                "with merges. file: %s", zName)
        /* does not return */;
    }
#endif
    while(pFile && fsl_strcmp(pFile->name, zName)<0){
      /* Baseline has files with lexically smaller names.
         Interesting corner case:

         f-rm th1ish/makefile.gnu
         f-checkin ... th1ish/makefile.gnu

         makefile.gnu does not get picked up by the historical query
         but gets picked up here. We really need to ++changeCounter in
         that case, but we don't know we're in that case because we're
         now traversing a filename which is not in the result set.
         The end result (because we don't increment changeCounter) is
         that fsl_checkin_commit() thinks we have no made any changes
         and errors out. If we ++changeCounter for all deletions we
         have a different corner case, where a no-change commit is not
         seen as such because we've counted deletions from (other)
         versions between the baseline and the checkout.
      */
      rc = fsl_deck_F_add(d, pFile->name, NULL, pFile->perm, NULL);
      if(rc) break;
      fsl_deck_F_next(pBaseline, &pFile);
    }
    if(rc) goto end;
    else if(isSel && (changed || deleted || renamed)){
      /* MARKER(("isSel && (changed||deleted||renamed): %s\n", zName)); */
      ++changeCounter;
      if(deleted){
        zOrig = NULL;
      }else if(changed){
        rc = fsl_checkin_import_file(f, zName, rid,
                                     ciOpt->allowMergeConflict,
                                     &fileBlobRid, &fUuid);
        if(!rc) rc = fsl_checkin_add_unsent(f, fileBlobRid);
        RC;
        /* MARKER(("New content: %d / %s / %s\n", (int)fileBlobRid, fUuid, zName)); */
        if(0 != fsl_uuidcmp(zUuid, fUuid)){
          zUuid = fUuid;
        }
        fsl_stmt_reset(&stUpdateFileRid);
        fsl_stmt_bind_id(&stUpdateFileRid, 1, fileBlobRid);
        fsl_stmt_bind_id(&stUpdateFileRid, 2, id);
        if(FSL_RC_STEP_DONE!=fsl_stmt_step(&stUpdateFileRid)){
          rc = fsl_cx_uplift_db_error(f, stUpdateFileRid.db);
          assert(rc);
          goto end;
        }
      }else{
        assert(renamed);
        assert(zOrig);
      }
    }
    assert(!rc);
    cmp = 1;
    if(!pFile
       || (cmp = fsl_strcmp(pFile->name,zName))!=0
       /* ^^^^ the cmp assignment must come right after (!pFile)! */
       || deleted
       || (perm != pFile->perm)/* permissions change */
       || fsl_strcmp(pFile->uuid, zUuid)!=0
       /* ^^^^^ file changed somewhere between baseline and delta */
       ){
      if(isSel && deleted){
        if(pBaseline /* d is-a delta */){
          /* Deltas mark deletions with F-cards having only 
             a file name (no UUID or permission).
          */
          rc = fsl_deck_F_add(d, zName, NULL, perm, NULL);
        }/*else elide F-card to mark a deletion in a baseline.*/
      }else{
        if(zOrig && !isSel){
          /* File is renamed in vfile but is not being committed, so
             make sure we use the original name for the F-card.
          */
          zName = zOrig;
          zOrig = NULL;
        }
        assert(zUuid);
        assert(fileBlobRid);
        if( !zOrig || !renamed ){
          rc = fsl_deck_F_add(d, zName, zUuid, perm, NULL);
        }else{
          /* Rename this file */
          rc = fsl_deck_F_add(d, zName, zUuid, perm, zOrig);
        }
      }
    }
    fsl_free(fUuid);
    fUuid = NULL;
    RC;
    if( 0 == cmp ){
      fsl_deck_F_next(pBaseline, &pFile);
    }
  }/*while step()*/

  while( !rc && pFile ){
    /* Baseline has remaining files with lexically larger names. Let's import them. */
    rc = fsl_deck_F_add(d, pFile->name, NULL, pFile->perm, NULL);
    if(!rc) fsl_deck_F_next(pBaseline, &pFile);
  }

  end:
#undef RC
  fsl_free(fUuid);
  fsl_stmt_finalize(q);
  fsl_stmt_finalize(&stUpdateFileRid);
  if(!rc && changeCount) *changeCount = changeCounter;
  return rc;
}

/**
   Cancels all symbolic tags (branches) on the given version by
   adding one T-card to d for each active branch tag set on vid.
   When creating a branch, d would represent the branch and vid
   would be the version being branched from.

   Returns 0 on success.
*/
static int fsl_cancel_sym_tags( fsl_deck * d, fsl_id_t vid ){
  int rc;
  fsl_stmt q = fsl_stmt_empty;
  fsl_db * db = fsl_needs_repo(d->f);
  assert(db);
  rc = fsl_db_prepare(db, &q,
                      "SELECT tagname FROM tagxref, tag"
                      " WHERE tagxref.rid=%"FSL_ID_T_PFMT
                      " AND tagxref.tagid=tag.tagid"
                      "   AND tagtype>0 AND tagname GLOB 'sym-*'"
                      " ORDER BY tagname",
                      (fsl_id_t)vid);
  while( !rc && (FSL_RC_STEP_ROW==fsl_stmt_step(&q)) ){
    const char *zTag = fsl_stmt_g_text(&q, 0, NULL);
    rc = fsl_deck_T_add(d, FSL_TAGTYPE_CANCEL,
                        NULL, zTag, "Cancelled by branch.");
  }
  fsl_stmt_finalize(&q);
  return rc;
}

#if 0
static int fsl_leaf_set( fsl_cx * f, fsl_id_t rid, char isLeaf ){
  int rc;
  fsl_stmt * st = NULL;
  fsl_db * db = fsl_needs_repo(f);
  assert(db);
  rc = fsl_db_prepare_cached(db, &st, isLeaf
                             ? "INSERT OR IGNORE INTO leaf(rid) VALUES(?)"
                             : "DELETE FROM leaf WHERE rid=?");
  if(!rc){
    fsl_stmt_bind_id(st, 1, rid);
    fsl_stmt_step(st);
    fsl_stmt_cached_yield(st);
  }
  if(rc){
    fsl_cx_uplift_db_error(f, db);
  }
  return rc;
}
#endif

/**
   Checks vfile for any files (where chnged in (2,3,4,5)), i.e.
   having something to do with a merge. If either all of those
   changes are enqueued for checkin, or none of them are, then
   this function returns 0, otherwise it sets f's error
   state and returns non-0.
*/
static int fsl_check_for_partial_merge(fsl_cx * f){
  if(!f->ckin.selectedIds.entryCount){
    /* All files are considered enqueued. */
    return 0;
  }else{
    fsl_db * db = fsl_cx_db_ckout(f);
    int32_t counter = 0;
    int rc =
      fsl_db_get_int32(db, &counter,
                       "SELECT COUNT(*) FROM ("
#if 1
                       "SELECT DISTINCT fsl_is_enqueued(id)"
                       " FROM vfile WHERE chnged IN (2,3,4,5)"
#else
                       "SELECT fsl_is_enqueued(id) isSel "
                       "FROM vfile WHERE chnged IN (2,3,4,5) "
                       "GROUP BY isSel"
#endif
                       ")"
                       );
    /**
       Result is 0 if no merged files are in vfile, 1 row if isSel is
       the same for all merge-modified files, and 2 if there is a mix
       of selected/unselected merge-modified files.
     */
    if(!rc && (counter>1)){
      assert(2==counter);
      rc = fsl_cx_err_set(f, FSL_RC_MISUSE,
                          "Only Chuck Norris can commit "
                          "a partial merge. Commit either all "
                          "or none of it.");
    }
    return rc;
  }
}

/**
   Populates d with the contents for a FSL_SATYPE_CHECKIN manifest
   based on repository version basedOnVid.

   d is the deck to populate.

   basedOnVid must currently be f->ckout.rid OR the vfile table must
   be current for basedOnVid (see fsl_vfile_changes_scan() and
   fsl_vfile_load()). It "should" work with basedOnVid==0 but
   that's untested so far.

   opt is the options object passed to fsl_checkin_commit().
*/
static int fsl_checkin_calc_manifest( fsl_cx * f, fsl_deck * d,
                                      fsl_id_t basedOnVid,
                                      fsl_checkin_opt const * opt ){
  int rc;
  fsl_db * dbR = fsl_cx_db_repo(f);
  fsl_db * dbC = fsl_cx_db_ckout(f);
  fsl_stmt q = fsl_stmt_empty;
  fsl_deck dBase = fsl_deck_empty;
  char const * zColor;
  int deltaPolicy = opt->deltaPolicy;
  assert(d->f == f);
  assert(FSL_SATYPE_CHECKIN==d->type);
#define RC if(rc) goto end
  /* assert(basedOnVid>0); */
  rc = (opt->message && *opt->message)
    ? fsl_deck_C_set( d, opt->message, -1 )
    : fsl_cx_err_set(f, FSL_RC_MISSING_INFO,
                     "Cowardly refusing to commit with "
                     "empty checkin comment.");
  RC;

  if(deltaPolicy!=0 && fsl_repo_forbids_delta_manifests(f)){
    deltaPolicy = 0;
  }else if(deltaPolicy<0 && f->cache.seenDeltaManifest<=0){
    deltaPolicy = 0;
  }
  {
    char const * zUser = opt->user ? opt->user : fsl_cx_user_get(f);
    rc = (zUser && *zUser)
      ? fsl_deck_U_set( d, zUser )
      : fsl_cx_err_set(f, FSL_RC_MISSING_INFO,
                       "Cowardly refusing to commit without "
                       "a user name.");
    RC;
  }

  rc = fsl_check_for_partial_merge(f);
  RC;

  rc = fsl_deck_D_set( d, (opt->julianTime>0)
                       ? opt->julianTime
                       : fsl_db_julian_now(dbR) );
  RC;

  if(opt->messageMimeType && *opt->messageMimeType){
    rc = fsl_deck_N_set( d, opt->messageMimeType, -1 );
    RC;
  }


  { /* F-cards */
    static char const * errNoFilesMsg =
      "No files have changed. Cowardly refusing to commit.";
    static int const errNoFilesRc = FSL_RC_NOOP;
    fsl_deck * pBase = NULL /* baseline for delta generation purposes */;
    fsl_size_t szD = 0, szB = 0 /* see commentary below */;
    if(basedOnVid && deltaPolicy!=0){
      /* Figure out a baseline for a delta manifest... */
      rc = fsl_deck_load_rid(f, &dBase, basedOnVid, FSL_SATYPE_CHECKIN);
      RC;
      if(dBase.B.uuid){
        /* dBase is a delta. Let's use its baseline for manifest
           generation.
        */
        fsl_id_t const baseRid = fsl_uuid_to_rid(f, dBase.B.uuid);
        fsl_deck_finalize(&dBase);
        assert(baseRid>0);
        rc = fsl_deck_load_rid(f, &dBase, baseRid,
                               FSL_SATYPE_CHECKIN);
        RC;
      }else{
        /* dBase version is a suitable baseline. */
      }
      pBase = &dBase;
      /* MARKER(("Baseline = %d / %s\n", (int)pBase->rid, pBase->uuid)); */
      rc = fsl_deck_B_set(d, pBase->uuid);
      RC;
    }
    rc = fsl_checkin_calc_F_cards2(f, d, pBase, basedOnVid,
                                   &szD, opt);
    /*MARKER(("szD=%d\n", (int)szD));*/
    RC;
    if(basedOnVid && !szD){
      rc = fsl_cx_err_set(f, errNoFilesRc, errNoFilesMsg);
      goto end;
    }
    szB = pBase ? pBase->F.used : 0;
    /* The following text was copied verbatim from fossil(1). It does
       not apply 100% here (because we use a slightly different
       manifest generation approach) but it clearly describes what's
       going on after the comment block....
    */
    /*
    ** At this point, two manifests have been constructed, either of
    ** which would work for this checkin.  The first manifest (held
    ** in the "manifest" variable) is a baseline manifest and the second
    ** (held in variable named "delta") is a delta manifest.  The
    ** question now is: which manifest should we use?
    **
    ** Let B be the number of F-cards in the baseline manifest and
    ** let D be the number of F-cards in the delta manifest, plus one for
    ** the B-card.  (B is held in the szB variable and D is held in the
    ** szD variable.)  Assume that all delta manifests adds X new F-cards.
    ** Then to minimize the total number of F- and B-cards in the repository,
    ** we should use the delta manifest if and only if:
    **
    **      D*D < B*X - X*X
    **
    ** X is an unknown here, but for most repositories, we will not be
    ** far wrong if we assume X=3.
    */
    ++szD /* account for the d->B card */;
    if(pBase){
      /* For this calculation, i believe the correct approach is to
         simply count the F-cards, including those changed between the
         baseline and the delta, as opposed to only those changed in
         the delta itself.
      */
      szD = 1 + d->F.used;
    }
    /* MARKER(("szB=%d szD=%d\n", (int)szB, (int)szD)); */
    if(pBase && (deltaPolicy<0/*non-force-mode*/
                 && !(((int)(szD*szD)) < (((int)szB*3)-9))
                 /* ^^^ see comments above */
                 )
       ){
      /* Too small of a delta to be worth it. Re-calculate
         F-cards with no baseline.

         Maintenance reminder: i initially wanted to update vfile's
         status incrementally as F-cards are calculated, but this
         discard/retry breaks on the retry because vfile's state has
         been modified. Thus instead of updating vfile incrementally,
         we re-scan it after the checkin completes.
      */
      fsl_deck tmp = fsl_deck_empty;
      /* Free up d->F using a kludge... */
      tmp.F = d->F;
      d->F = fsl_deck_empty.F;
      fsl_deck_finalize(&tmp);
      fsl_deck_B_set(d, NULL);
      /* MARKER(("Delta is too big - re-calculating F-cards for a baseline.\n")); */
      szD = 0;
      rc = fsl_checkin_calc_F_cards2(f, d, NULL, basedOnVid,
                                     &szD, opt);
      RC;
      if(basedOnVid && !szD){
        rc = fsl_cx_err_set(f, errNoFilesRc, errNoFilesMsg);
        goto end;
      }
    }
  }/* F-cards */

  /* parents... */
  if( basedOnVid ){
    char * zParentUuid = fsl_rid_to_artifact_uuid(f, basedOnVid, FSL_SATYPE_CHECKIN);
    if(!zParentUuid){
      assert(f->error.code);
      rc = f->error.code
        ? f->error.code
        : fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                         "Could not find checkin UUID "
                         "for RID %"FSL_ID_T_PFMT".",
                         basedOnVid);
      goto end;
    }
    rc = fsl_deck_P_add(d, zParentUuid)
      /* pedantic side-note: we could alternately transfer ownership
         of zParentUuid by fsl_list_append()ing it to d->P, but that
         would bypass e.g. any checking that routine chooses to apply.
      */;
    fsl_free(zParentUuid);
    /* if(!rc) rc = fsl_leaf_set(f, basedOnVid, 0); */
    /* TODO:
       if( p->verifyDate ) checkin_verify_younger(vid, zParentUuid, zDate); */
    RC;
    rc = fsl_db_prepare(dbC, &q, "SELECT merge FROM vmerge WHERE id=0 OR id<-2");
    RC;
    while( FSL_RC_STEP_ROW == fsl_stmt_step(&q) ){
      char *zMergeUuid;
      fsl_id_t const mid = fsl_stmt_g_id(&q, 0);
      //MARKER(("merging? %d\n", (int)mid));
      if( (mid == basedOnVid)
          || (!f->cache.markPrivate && fsl_content_is_private(f,mid))){
        continue;
      }
      zMergeUuid = fsl_rid_to_uuid(f, mid)
        /* FIXME? Adjust the query to join on blob and return the UUID? */
        ;
      //MARKER(("merging %d %s\n", (int)mid, zMergeUuid));
      if(zMergeUuid){
        rc = fsl_deck_P_add(d, zMergeUuid);
        fsl_free(zMergeUuid);
      }
      RC;
      /* TODO:
         if( p->verifyDate ) checkin_verify_younger(mid, zMergeUuid, zDate); */
    }
    fsl_stmt_finalize(&q);
  }

  { /* Q-cards... */
    rc = fsl_db_prepare(dbR, &q,
                        "SELECT "
                        "CASE vmerge.id WHEN -1 THEN '+' ELSE '-' END || mhash,"
                        "  merge"
                        "  FROM vmerge"
                        " WHERE (vmerge.id=-1 OR vmerge.id=-2)"
                        " ORDER BY 1");
    while( !rc && (FSL_RC_STEP_ROW==fsl_stmt_step(&q)) ){
      fsl_id_t const mid = fsl_stmt_g_id(&q, 1);
      if( mid != basedOnVid ){
        const char *zCherrypickUuid = fsl_stmt_g_text(&q, 0, NULL);
        int const qType = '+'==*(zCherrypickUuid++) ? 1 : -1;
        rc = fsl_deck_Q_add( d, qType, zCherrypickUuid, NULL );
      }
    }
    fsl_stmt_finalize(&q);
    RC;
  }

  zColor = opt->bgColor;
  if(opt->branch && *opt->branch){
    char * sym = fsl_mprintf("sym-%s", opt->branch);
    if(!sym){
      rc = FSL_RC_OOM;
      goto end;
    }
    rc = fsl_deck_T_add( d, FSL_TAGTYPE_PROPAGATING,
                         NULL, sym, NULL );
    fsl_free(sym);
    RC;
    if(opt->bgColor && *opt->bgColor){
      zColor = NULL;
      rc = fsl_deck_T_add( d, FSL_TAGTYPE_PROPAGATING,
                           NULL, "bgcolor", opt->bgColor);
      RC;
    }
    rc = fsl_deck_T_add( d, FSL_TAGTYPE_PROPAGATING,
                         NULL, "branch", opt->branch );
    RC;
    if(basedOnVid){
      rc = fsl_cancel_sym_tags(d, basedOnVid);
    }
  }
  if(zColor && *zColor){
    /* One-shot background color */
    rc = fsl_deck_T_add( d, FSL_TAGTYPE_ADD,
                         NULL, "bgcolor", opt->bgColor);
    RC;
  }

  if(opt->closeBranch){
    rc = fsl_deck_T_add( d, FSL_TAGTYPE_ADD,
                         NULL, "closed",
                         *opt->closeBranch
                         ? opt->closeBranch
                         : NULL);
    RC;
  }

  {
    /*
      Close any INTEGRATE merges if !op->integrate, or type-0 and
      integrate merges if opt->integrate.
    */
    rc = fsl_db_prepare(dbC, &q,
                        "SELECT mhash, merge FROM vmerge "
                        " WHERE id %s ORDER BY 1",
                        opt->integrate ? "IN(0,-4)" : "=(-4)");
    while( !rc && (FSL_RC_STEP_ROW==fsl_stmt_step(&q)) ){
      fsl_id_t const rid = fsl_stmt_g_id(&q, 1);
      //MARKER(("Integrating %d? opt->integrate=%d\n",(int)rid, opt->integrate));
      if( fsl_rid_is_leaf(f, rid)
          && !fsl_db_exists(dbR, /* Is not closed already... */
                            "SELECT 1 FROM tagxref "
                            "WHERE tagid=%d AND rid=%"FSL_ID_T_PFMT
                            " AND tagtype>0",
                            FSL_TAGID_CLOSED, rid)){
        const char *zIntegrateUuid = fsl_stmt_g_text(&q, 0, NULL);
        //MARKER(("Integrating %d %s\n",(int)rid, zIntegrateUuid));
        rc = fsl_deck_T_add( d, FSL_TAGTYPE_ADD, zIntegrateUuid,
                             "closed", "Closed by integrate-merge." );
      }
    }
    fsl_stmt_finalize(&q);
    RC;
  }

  end:
#undef RC
  fsl_stmt_finalize(&q);
  fsl_deck_finalize(&dBase);
  d->B.baseline = NULL /* if it was set, it was &dBase */;
  if(rc && !f->error.code){
    if(dbR->error.code) fsl_cx_uplift_db_error(f, dbR);
    else if(dbC->error.code) fsl_cx_uplift_db_error(f, dbC);
    else if(f->dbMain->error.code) fsl_cx_uplift_db_error(f, f->dbMain);
  }
  return rc;
}

int fsl_checkin_T_add2( fsl_cx * f, fsl_card_T * t){
  return fsl_deck_T_add2( &f->ckin.mf, t );
}

int fsl_checkin_T_add( fsl_cx * f, fsl_tagtype_e tagType,
                       fsl_uuid_cstr uuid, char const * name,
                       char const * value){
  return fsl_deck_T_add( &f->ckin.mf, tagType, uuid, name, value );
}

/**
   Returns true if the given blob RID is has a "closed" tag. This is
   generally intended only to be passed the RID of the current
   checkout, before attempting to perform a commit against it.
*/
static bool fsl_leaf_is_closed(fsl_cx * f, fsl_id_t rid){
  fsl_db * const dbR = fsl_needs_repo(f);
  return dbR
    ? fsl_db_exists(dbR, "SELECT 1 FROM tagxref"
                    " WHERE tagid=%d "
                    " AND rid=%"FSL_ID_T_PFMT" AND tagtype>0",
                    FSL_TAGID_CLOSED, rid)
    : false;
}

/**
   Returns true if the given name is the current branch
   for the given checkin version.
 */
static bool fsl_is_current_branch(fsl_db * dbR, fsl_id_t vid,
                                  char const * name){
  return fsl_db_exists(dbR,
                       "SELECT 1 FROM tagxref"
                       " WHERE tagid=%d AND rid=%"FSL_ID_T_PFMT
                       " AND tagtype>0"
                       " AND value=%Q",
                       FSL_TAGID_BRANCH, vid, name);
}

int fsl_checkin_commit(fsl_cx * f, fsl_checkin_opt const * opt,
                       fsl_id_t * newRid, fsl_uuid_str * newUuid ){
  int rc;
  fsl_deck deck = fsl_deck_empty;
  fsl_deck *d = &deck;
  fsl_db * dbC;
  fsl_db * dbR;
  char inTrans = 0;
  char oldPrivate;
  int const oldFlags = f ? f->flags : 0;
  fsl_id_t const vid = f ? f->ckout.rid : 0;
  if(!f || !opt) return FSL_RC_MISUSE;
  else if(!(dbC = fsl_needs_ckout(f))) return FSL_RC_NOT_A_CKOUT;
  else if(!(dbR = fsl_needs_repo(f))) return FSL_RC_NOT_A_REPO;
  assert(vid>=0);
  /**
     Do not permit a checkin to a closed leaf unless opt->branch would
     switch us to a new branch.
  */
  if( fsl_leaf_is_closed(f, vid)
      && (!opt->branch || !*opt->branch
          || fsl_is_current_branch(dbR, vid, opt->branch))){
    return fsl_cx_err_set(f, FSL_RC_ACCESS,
                          "Only Chuck Norris can commit to "
                          "a closed leaf.");
  }

  if(vid && opt->scanForChanges){
    /* We need to ensure this state is current in order to determine
       whether a given file is locally modified vis-a-vis the
       commit-time vfile state. */
    rc = fsl_vfile_changes_scan(f, vid, 0);
    if(rc) return rc;
  }

  fsl_cx_err_reset(f) /* avoid propagating an older error by accident.
                         Did that in test code. */;

  oldPrivate = f->cache.markPrivate;
  if(opt->isPrivate || fsl_content_is_private(f, vid)){
    f->cache.markPrivate = 1;
  }

#define RC if(rc) goto end
  fsl_deck_init(f, d, FSL_SATYPE_CHECKIN);

  rc = fsl_db_transaction_begin(dbR);
  RC;
  inTrans = 1;
  if(f->ckin.mf.T.used){
    /* Transfer accumulated tags. */
    assert(!f->ckin.mf.content.used);
    d->T = f->ckin.mf.T;
    f->ckin.mf.T = fsl_deck_empty.T;
  }
  rc = fsl_checkin_calc_manifest(f, d, vid, opt);
  RC;
  if(!d->F.used){
    rc = fsl_cx_err_set(f, FSL_RC_NOOP,
                        "Cowardly refusing to generate an empty commit.");
    RC;
  }

  if(opt->calcRCard) f->flags |= FSL_CX_F_CALC_R_CARD;
  else f->flags &= ~FSL_CX_F_CALC_R_CARD;
  rc = fsl_deck_save( d, opt->isPrivate );
  RC;
  assert(d->rid>0);
  assert(d->uuid);
  /* Now get vfile back into shape. We do not do a vfile scan
     because that loses state like add/rm-queued files. */
  rc = fsl_db_exec_multi(dbC,
                         "DELETE FROM vfile WHERE vid<>"
                         "%" FSL_ID_T_PFMT ";"
                         "UPDATE vfile SET vid=%" FSL_ID_T_PFMT ";"
                         "DELETE FROM vfile WHERE deleted AND "
                         "fsl_is_enqueued(id); "
                         "UPDATE vfile SET rid=mrid, mhash=NULL, "
                         "chnged=0, deleted=0, origname=NULL "
                         "WHERE fsl_is_enqueued(id)",
                         vid, d->rid);
  if(!rc) rc = fsl_ckout_version_write(f, d->rid, d->uuid);
  RC;
  assert(d->f == f);
  rc = fsl_checkin_add_unsent(f, d->rid);
  RC;
  rc = fsl_ckout_clear_merge_state(f);
  RC;
  /*
    todo(?) from fossil(1) follows. Most of this seems to be what the
    vfile handling does (above).

    db_multi_exec("PRAGMA %s.application_id=252006673;", db_name("repository"));
    db_multi_exec("PRAGMA %s.application_id=252006674;", db_name("localdb"));

    // Update the vfile and vmerge tables
    db_multi_exec(
      "DELETE FROM vfile WHERE (vid!=%d OR deleted) AND is_selected(id);"
      "DELETE FROM vmerge;"
      "UPDATE vfile SET vid=%d;"
      "UPDATE vfile SET rid=mrid, chnged=0, deleted=0, origname=NULL"
      " WHERE is_selected(id);"
      , vid, nvid
    );
    db_lset_int("checkout", nvid);


    // Update the isexe and islink columns of the vfile table
    db_prepare(&q,
      "UPDATE vfile SET isexe=:exec, islink=:link"
      " WHERE vid=:vid AND pathname=:path AND (isexe!=:exec OR islink!=:link)"
    );
    db_bind_int(&q, ":vid", nvid);
    pManifest = manifest_get(nvid, CFTYPE_MANIFEST, 0);
    manifest_file_rewind(pManifest);
    while( (pFile = manifest_file_next(pManifest, 0)) ){
      db_bind_int(&q, ":exec", pFile->zPerm && strstr(pFile->zPerm, "x"));
      db_bind_int(&q, ":link", pFile->zPerm && strstr(pFile->zPerm, "l"));
      db_bind_text(&q, ":path", pFile->zName);
      db_step(&q);
      db_reset(&q);
    }
    db_finalize(&q);
  */

  if(opt->dumpManifestFile){
    FILE * out;
    /* MARKER(("Dumping generated manifest to file [%s]:\n", opt->dumpManifestFile)); */
    out = fsl_fopen(opt->dumpManifestFile, "w");
    if(out){
      rc = fsl_deck_output( d, fsl_output_f_FILE, out );
      fsl_fclose(out);
    }else{
      rc = fsl_cx_err_set(f, FSL_RC_IO, "Could not open output "
                          "file for writing: %s", opt->dumpManifestFile);
    }
    RC;
  }

  if(d->P.used){
    /* deltify the parent manifest */
    char const * p0 = (char const *)d->P.list[0];
    fsl_id_t const prid = fsl_uuid_to_rid(f, p0);
    /* MARKER(("Deltifying parent manifest #%d...\n", (int)prid)); */
    assert(p0);
    assert(prid>0);
    rc = fsl_content_deltify(f, prid, d->rid, 0);
    RC;
  }

  end:
  f->flags = oldFlags;
#undef RC
  f->cache.markPrivate = oldPrivate;
  /* fsl_buffer_reuse(&f->fileContent); */
  if(inTrans){
    if(rc) fsl_db_transaction_rollback(dbR);
    else{
      rc = fsl_db_transaction_commit(dbR);
      if(!rc){
        if(newRid) *newRid = d->rid;
        if(newUuid){
          *newUuid = d->uuid;
          d->uuid = NULL /* transfer ownership */;
        }
      }
    }
  }
  if(rc && !f->error.code){
    if(dbR->error.code) fsl_cx_uplift_db_error(f, dbR);
    else if(f->dbMain->error.code) fsl_cx_uplift_db_error(f, f->dbMain);
  }
  fsl_checkin_discard(f);
  fsl_deck_finalize(d);
  return rc;
}


#undef MARKER
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<








































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/checkout.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 houses the code for checkout-level APIS.
*/
#include <assert.h>

#include "fossil-scm/fossil-internal.h"
#include "fossil-scm/fossil-core.h"
#include "fossil-scm/fossil-checkout.h"
#include "fossil-scm/fossil-hash.h"
#include "fossil-scm/fossil-confdb.h"
#include <string.h> /* memcmp() */

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


/**
    Kludge for type-safe strncmp/strnicmp inconsistency.
*/
static int fsl_strnicmp_int(char const *zA, char const * zB, fsl_size_t nByte){
  return fsl_strnicmp( zA, zB, (fsl_int_t)nByte);
}

int fsl_ckout_filename_check( fsl_cx * f, bool relativeToCwd,
                              char const * zOrigName, fsl_buffer * pOut ){
  int rc;
  if(!zOrigName || !*zOrigName) return FSL_RC_MISUSE;
  else if(!fsl_needs_ckout(f)/* will update f's error state*/){
    return FSL_RC_NOT_A_CKOUT;
  }
#if 0
  /* Is this sane? */
  else if(fsl_is_simple_pathname(zOrigName,1)){
    rc = 0;
    if(pOut){
      rc = fsl_buffer_append(pOut, zOrigName, fsl_strlen(zOrigName));
    }
  }
#endif
  else{
    char const * zLocalRoot;
    char const * zFull;
    fsl_size_t nLocalRoot;
    fsl_size_t nFull;
    fsl_buffer * full = fsl_cx_scratchpad(f);
    int (*xCmp)(char const *, char const *,fsl_size_t);
    bool endsWithSlash;
    assert(f->ckout.dir);
    zLocalRoot = f->ckout.dir;
    assert(zLocalRoot);
    assert(*zLocalRoot);
    nLocalRoot = f->ckout.dirLen;
    assert(nLocalRoot);
    assert('/' == zLocalRoot[nLocalRoot-1]);
    rc = fsl_file_canonical_name2(relativeToCwd ? NULL : zLocalRoot,
                                  zOrigName, full, 1);
#if 0
    MARKER(("canon2: %p (%s) %s ==> %s\n", (void const *)full->mem,
            relativeToCwd ? "cwd" : "ckout", zOrigName, fsl_buffer_cstr(full)));
#endif
    if(rc){
      if(FSL_RC_OOM != rc){
        rc = fsl_cx_err_set(f, rc, "Error #%d (%s) canonicalizing "
                            "file name: %s\n",
                            rc, fsl_rc_cstr(rc),
                            zOrigName);
      }
      goto end;
    }
    zFull = fsl_buffer_cstr2(full, &nFull);
    xCmp = fsl_cx_is_case_sensitive(f)
      ? fsl_strncmp
      : fsl_strnicmp_int;
    assert(zFull);
    assert(nFull>0);
    endsWithSlash = '/' == zFull[nFull-1];
    if( ((nFull==nLocalRoot-1 || (nFull==nLocalRoot && endsWithSlash))
         && xCmp(zLocalRoot, zFull, nFull)==0)
        || (nFull==1 && zFull[0]=='/' && nLocalRoot==1 && zLocalRoot[0]=='/') ){
      /* Special case.  zOrigName refers to zLocalRoot directory.

         Outputing "." instead of nothing is a historical decision
         which may be worth re-evaluating. Currently fsl_cx_stat() relies
         on it.
      */
      if(pOut){
        char const * zOut;
        fsl_size_t nOut;
        if(endsWithSlash){ /* retain trailing slash */
          zOut = "./";
          nOut = 2;
        }else{
          zOut = ".";
          nOut = 1;
        };
        rc = fsl_buffer_append(pOut, zOut, nOut);
      }else{
        rc = 0;
      }
      goto end;
    }

    if( nFull<=nLocalRoot || xCmp(zLocalRoot, zFull, nLocalRoot) ){
      rc = fsl_cx_err_set(f, FSL_RC_RANGE,
                          "File is outside of checkout tree: %s",
                          zOrigName);
      goto end;
    }

    if(pOut){
      rc = fsl_buffer_append(pOut, zFull + nLocalRoot, nFull - nLocalRoot);
    }

    end:
    fsl_cx_scratchpad_yield(f, full);
  }
  return rc;
}


/**
    Returns a fsl_ckout_change_e value for the given
    fsl_vfile_change_e value.

    Why are these not consolidated into one enum?  2021-03-13: because
    there are more checkout-level change codes than vfile-level
    changes. We could still consolidate them, giving the vfile changes
    their hard-coded values and leaving room in the enum for upward
    growth of that set.
*/
static fsl_ckout_change_e fsl_vfile_to_ckout_change(int vChange){
  switch((fsl_vfile_change_e)vChange){
#define EE(X) case FSL_VFILE_CHANGE_##X: return FSL_CKOUT_CHANGE_##X
    EE(NONE);
    EE(MOD);
    EE(MERGE_MOD);
    EE(MERGE_ADD);
    EE(INTEGRATE_MOD);
    EE(INTEGRATE_ADD);
    EE(IS_EXEC);
    EE(BECAME_SYMLINK);
    EE(NOT_EXEC);
    EE(NOT_SYMLINK);
#undef EE
    default:
       assert(!"Unhandled fsl_vfile_change_e value!");
      return FSL_CKOUT_CHANGE_NONE;
  }
}

int fsl_ckout_changes_visit( fsl_cx * f, fsl_id_t vid,
                             bool doScan,
                             fsl_ckout_changes_f visitor,
                             void * state ){
  int rc;
  fsl_db * db;
  fsl_stmt st = fsl_stmt_empty;
  int count = 0;
  fsl_ckout_change_e coChange;
  fsl_fstat fstat;
  if(!f || !visitor) return FSL_RC_MISUSE;
  db = fsl_needs_ckout(f);
  if(!db) return FSL_RC_NOT_A_CKOUT;
  if(vid<0){
    vid = f->ckout.rid;
    assert(vid>=0);
  }
  if(doScan){
    rc = fsl_vfile_changes_scan(f, vid, 0);
    if(rc) goto end;
  }
  rc = fsl_db_prepare(db, &st,
                      "SELECT chnged, deleted, rid, "
                      "pathname, origname "
                      "FROM vfile WHERE vid=%" FSL_ID_T_PFMT
                      " /*%s()*/",
                      vid,__func__);
  assert(!rc);
  while( FSL_RC_STEP_ROW == fsl_stmt_step(&st) ){
    int const changed = fsl_stmt_g_int32(&st, 0);
    int const deleted = fsl_stmt_g_int32(&st,1);
    fsl_id_t const vrid = fsl_stmt_g_id(&st,2);
    char const * name;
    char const * oname = NULL;
    name = fsl_stmt_g_text(&st, 3, NULL);
    oname = fsl_stmt_g_text(&st,4,NULL);
    if(oname && (0==fsl_strcmp(name, oname))){
      /* Work around a fossil oddity which sets origname=pathname
         during a 'mv' operation.
      */
      oname = NULL;
    }
    coChange = FSL_CKOUT_CHANGE_NONE;
    if(deleted){
      coChange = FSL_CKOUT_CHANGE_REMOVED;
    }else if(0==vrid){
      coChange = FSL_CKOUT_CHANGE_ADDED;
    }else if(!changed && NULL != oname){
      /* In fossil ^^, the "changed" state trumps the "renamed" state
       for status view purposes, so we'll do that here. */
      coChange = FSL_CKOUT_CHANGE_RENAMED;
    }else{
      fstat = fsl_fstat_empty;
      if( fsl_cx_stat(f, false, name, &fstat ) ){
        coChange = FSL_CKOUT_CHANGE_MISSING;
        fsl_cx_err_reset(f) /* keep FSL_RC_NOT_FOUND from bubbling
                               up to the client! */;
      }else if(!changed){
        continue;
      }else{
        coChange = fsl_vfile_to_ckout_change(changed);
      }
    }
    if(!coChange){
      MARKER(("INTERNAL ERROR: unhandled vfile.chnged "
              "value %d for file [%s]\n",
              changed, name));
      continue;
    }
    ++count;
    rc = visitor(state, coChange, name, oname);
    if(rc){
      if(FSL_RC_BREAK==rc){
        rc = 0;
        break;
      }else if(!f->error.code && (FSL_RC_OOM!=rc)){
        fsl_cx_err_set(f, rc, "Error %s returned from changes callback.",
                       fsl_rc_cstr(rc));
      }
      break;
    }
  }
  end:
  fsl_stmt_finalize(&st);
  if(rc && db->error.code && !f->error.code){
    fsl_cx_uplift_db_error(f, db);
  }

  return rc;
}

static bool fsl_co_is_in_vfile(fsl_cx *f,
                               char const *zFilename){
  return fsl_db_exists(fsl_cx_db_ckout(f),
                       "SELECT 1 FROM vfile"
                       " WHERE vid=%"FSL_ID_T_PFMT
                       " AND pathname=%Q %s",
                       f->ckout.rid, zFilename,
                       fsl_cx_filename_collation(f));
}
/**
   Internal machinery for fsl_ckout_manage(). zFilename MUST
   be a checkout-relative file which is known to exist. fst MUST
   be an object populated by fsl_stat()'ing zFilename. isInVFile
   MUST be the result of having passed zFilename to fsl_co_is_in_vfile().
 */
static int fsl_ckout_manage_impl( fsl_cx * f, char const *zFilename,
                                       fsl_fstat const *fst,
                                       bool isInVFile){
  int rc = 0;
  fsl_db * const db = fsl_needs_ckout(f);
  assert(fsl_is_simple_pathname(zFilename, true));
  if( isInVFile ){
    rc = fsl_db_exec(db, "UPDATE vfile SET deleted=0,"
                     " mtime=%"PRIi64
                     " WHERE vid=%"FSL_ID_T_PFMT
                     " AND pathname=%Q %s",
                     (int64_t)fst->mtime,
                     f->ckout.rid, zFilename,
                     fsl_cx_filename_collation(f));
  }else{
    int const chnged = FSL_VFILE_CHANGE_MOD
      /* fossil(1) sets chnged=0 on 'add'ed vfile records, but then the 'status'
         command updates the field to 1. To avoid down-stream inconsistencies
         (such as the ones which lead me here), we'll go ahead and set it to
         1 here.
      */;
    rc = fsl_db_exec(db,
                     "INSERT INTO "
                     "vfile(vid,chnged,deleted,rid,mrid,pathname,isexe,islink,mtime)"
                     "VALUES(%"FSL_ID_T_PFMT",%d,0,0,0,%Q,%d,%d,%"PRIi64")",
                     f->ckout.rid, chnged, zFilename,
                     (FSL_FSTAT_PERM_EXE==fst->perm) ? 1 : 0,
                     (FSL_FSTAT_TYPE_LINK==fst->type) ? 1 : 0,
                     (int64_t)fst->mtime
                     );
  }
  if(rc) rc = fsl_cx_uplift_db_error2(f, db, rc);
  return rc;
}

/**
   Internal state for the recursive file-add process.
*/
struct CoAddState {
  fsl_cx * f;
  fsl_ckout_manage_opt * opt;
  fsl_buffer * absBuf; // absolute path of file to check
  fsl_buffer * coRelBuf; // checkout-relative path of absBuf
  fsl_fstat fst; // fsl_stat() state of absBuf's file
};
typedef struct CoAddState CoAddState;
static const CoAddState CoAddState_empty =
  {NULL, NULL, NULL, NULL, fsl_fstat_empty_m};

/**
   fsl_dircrawl_f() impl for recursively adding files to a
   repo. state must be a (CoAddState*)/
*/
static int fsl_dircrawl_f_add(fsl_dircrawl_state const *);

/**
   Attempts to add file or directory (recursively) cas->absBuf to the
   current repository. isCrawling must be true if this is a
   fsl_dircrawl()-invoked call, else false.
*/
static int co_add_one(CoAddState * cas, bool isCrawling){
  int rc = 0;
  fsl_buffer_reuse(cas->coRelBuf);
  rc = fsl_cx_stat2(cas->f, cas->opt->relativeToCwd,
                    fsl_buffer_cstr(cas->absBuf), &cas->fst,
                    fsl_buffer_reuse(cas->coRelBuf), false)
    /* Reminder: will fail if file is outside of the checkout tree */;
  if(rc) return rc;
  switch(cas->fst.type){
    case FSL_FSTAT_TYPE_FILE:{
      bool skipped = false;
      char const * zCoRel = fsl_buffer_cstr(cas->coRelBuf);
      bool const isInVFile = fsl_co_is_in_vfile(cas->f, zCoRel);
      if(!isInVFile){
        if(fsl_reserved_fn_check(cas->f, zCoRel,-1,false)){
          /* ^^^ we need to use fsl_reserved_fn_check(), instead of
             fsl_is_reserved_fn(), so that we will inherit any
             new checks which require a context object. If that
             check fails, though, it updates cas->f with an error
             message which we need to suppress here to avoid it
             accidentally propagating and causing downstream
             confusion. */
          fsl_cx_err_reset(cas->f);
          skipped = true;
        }else if(cas->opt->checkIgnoreGlobs){
          char const * m =
            fsl_cx_glob_matches(cas->f, FSL_GLOBS_IGNORE, zCoRel);
          if(m) skipped = true;
        }
        if(!skipped && cas->opt->callback){
          bool yes = false;
          rc = cas->opt->callback( zCoRel, &yes,
                                   cas->opt->callbackState );
          if(rc) goto end;
          else if(!yes) skipped = true;
        }
      }
      if(skipped){
        ++cas->opt->counts.skipped;
      }else{
        rc = fsl_ckout_manage_impl(cas->f, zCoRel, &cas->fst,
                                        isInVFile);
        if(!rc){
          if(isInVFile) ++cas->opt->counts.updated;
          else ++cas->opt->counts.added;
        }
      }
      break;
    }
    case FSL_FSTAT_TYPE_DIR:
      if(!isCrawling){
        /* Reminder to self: fsl_dircrawl() copies its first argument
           for canonicalizing it, so this is safe even though
           cas->absBuf may be reallocated during the recursive
           call. We're done with these particular contents of
           cas->absBuf at this point. */
        rc = fsl_dircrawl(fsl_buffer_cstr(cas->absBuf),
                          fsl_dircrawl_f_add, cas);
        if(rc && !cas->f->error.code){
          rc = fsl_cx_err_set(cas->f, rc, "fsl_dircrawl() returned %s.",
                              fsl_rc_cstr(rc));
        }
      }else{
        assert(!"Cannot happen - caught higher up");
        fsl_fatal(FSL_RC_ERROR, "Internal API misuse in/around %s().",
                  __func__);
      }
      break;
    default:
      rc = fsl_cx_err_set(cas->f, FSL_RC_TYPE,
                          "Unhandled filesystem entry type: "
                          "fsl_fstat_type_e #%d", cas->fst.type);
      break;
  }
  end:
  return rc;
}

static int fsl_dircrawl_f_add(fsl_dircrawl_state const *dst){
  if(FSL_FSTAT_TYPE_FILE!=dst->entryType) return 0;
  CoAddState * cas = (CoAddState*)dst->callbackState;
  int rc = fsl_buffer_appendf(fsl_buffer_reuse(cas->absBuf),
                              "%s/%s", dst->absoluteDir, dst->entryName);
  if(!rc) rc = co_add_one(cas, true);
  return rc;
}

int fsl_ckout_manage( fsl_cx * f, fsl_ckout_manage_opt * opt_ ){
  int rc = 0;
  CoAddState cas = CoAddState_empty;
  fsl_ckout_manage_opt opt;
  if(!f) return FSL_RC_MISUSE;
  else if(!fsl_needs_ckout(f)) return FSL_RC_NOT_A_CKOUT;
  assert(f->ckout.rid>=0);
  opt = *opt_
    /*use a copy in case the user manages to modify
      opt_ from a callback. */;
  cas.absBuf = fsl_cx_scratchpad(f);
  cas.coRelBuf = fsl_cx_scratchpad(f);
  rc = fsl_file_canonical_name(opt.filename, cas.absBuf, false);
  if(!rc){
    cas.f = f;
    cas.opt = &opt;
    rc = co_add_one(&cas, false);
    opt_->counts = opt.counts;
  }
  fsl_cx_scratchpad_yield(f, cas.absBuf);
  fsl_cx_scratchpad_yield(f, cas.coRelBuf);
  return rc;
}

/**
   Creates, if needed, a TEMP TABLE named [tableName] with a single
   [id] field and populates it with all ids from the given bag.

   Returns 0 on success, any number of non-0 codes on error.
*/
static int fsl_ckout_bag_to_ids(fsl_cx *f, fsl_db * db,
                                char const * tableName,
                                fsl_id_bag const * bag){
  fsl_stmt insId = fsl_stmt_empty;
  int rc = fsl_db_exec_multi(db,
                             "CREATE TEMP TABLE IF NOT EXISTS "
                             "[%s](id); "
                             "DELETE FROM [%s] /* %s() */;",
                             tableName, tableName, __func__);
  if(rc) goto dberr;
  rc = fsl_db_prepare(db, &insId,
                      "INSERT INTO [%s](id) values(?1) "
                      "/* %s() */", tableName, __func__);
  if(rc) goto dberr;
  for(fsl_id_t e = fsl_id_bag_first(bag);
      e; e = fsl_id_bag_next(bag, e)){
    fsl_stmt_bind_id(&insId, 1, e);
    rc = fsl_stmt_step(&insId);
    switch(rc){
      case FSL_RC_STEP_DONE:
        rc = 0;
        break;
      default:
        fsl_stmt_finalize(&insId);
        goto dberr;
    }
    fsl_stmt_reset(&insId);
  }
  assert(!rc);
  end:
  fsl_stmt_finalize(&insId);
  return rc;
  dberr:
  assert(rc);
  rc = fsl_cx_uplift_db_error2(f, db, rc);
  goto end;
}

int fsl_ckout_unmanage(fsl_cx * f, fsl_ckout_unmanage_opt const * opt){
  int rc;
  fsl_db * const db = fsl_needs_ckout(f);
  fsl_buffer * fname = 0;
  fsl_id_t const vid = f->ckout.rid;
  fsl_stmt q = fsl_stmt_empty;
  bool inTrans = false;
  if(!db) return FSL_RC_NOT_A_CKOUT;
  else if((!opt->filename || !*opt->filename)
          && !opt->vfileIds){
    return fsl_cx_err_set(f, FSL_RC_MISUSE,
                          "Empty file set is not legal for %s()",
                          __func__);
  }
  assert(vid>=0);
  rc = fsl_db_transaction_begin(db);
  if(rc) goto dberr;
  inTrans = true;
  if(opt->vfileIds){
    rc = fsl_ckout_bag_to_ids(f, db, "fx_unmanage_id", opt->vfileIds);
    if(rc) goto end;
    rc = fsl_db_exec(db,
                     "UPDATE vfile SET deleted=1 "
                     "WHERE vid=%" FSL_ID_T_PFMT " "
                     "AND NOT deleted "
                     "AND id IN fx_unmanage_id /* %s() */",
                     vid, __func__);
    if(rc) goto dberr;
    if(opt->callback){
      rc = fsl_db_prepare(db,&q,
                          "SELECT pathname FROM vfile "
                          "WHERE vid=%" FSL_ID_T_PFMT " "
                          "AND deleted "
                          "AND id IN fx_unmanage_id "
                          "/* %s() */",
                          vid, __func__);
      if(rc) goto dberr;
    }
  }else{// Process opt->filename
    fname = fsl_cx_scratchpad(f);
    rc = fsl_ckout_filename_check(f, opt->relativeToCwd,
                                  opt->filename, fname);
    if(rc) goto end;
    char const * zNorm = fsl_buffer_cstr(fname);
    /* MARKER(("fsl_ckout_unmanage(%d, %s) ==> %s\n", relativeToCwd, zFilename, zNorm)); */
    assert(zNorm);
    if(fname->used){
      fsl_buffer_strip_slashes(fname);
      if(1==fname->used && '.'==*zNorm){
        /* Special case: handle "." from ckout root intuitively */
        fsl_buffer_reuse(fname);
        assert(0==*zNorm);
      }
    }
    rc = fsl_db_exec(db,
                     "UPDATE vfile SET deleted=1 "
                     "WHERE vid=%" FSL_ID_T_PFMT " "
                     "AND NOT deleted "
                     "AND CASE WHEN %Q='' THEN 1 "
                     "ELSE fsl_match_vfile_or_dir(pathname,%Q) "
                     "END /*%s()*/",
                     vid, zNorm, zNorm, __func__);
    if(rc) goto dberr;
    if(opt->callback){
      rc = fsl_db_prepare(db,&q,
                          "SELECT pathname FROM vfile "
                          "WHERE vid=%" FSL_ID_T_PFMT " "
                          "AND deleted "
                          "AND CASE WHEN %Q='' THEN 1 "
                          "ELSE fsl_match_vfile_or_dir(pathname,%Q) "
                          "END "
                          "UNION "
                          "SELECT pathname FROM vfile "
                          "WHERE vid=%" FSL_ID_T_PFMT " "
                          "AND rid=0 AND deleted "
                          "/*%s()*/",
                          vid, zNorm, zNorm, vid, __func__);
      if(rc) goto dberr;
    }
  }/*opt->filename*/

  if(q.stmt){
    while(FSL_RC_STEP_ROW==fsl_stmt_step(&q)){
      char const * fn = fsl_stmt_g_text(&q, 0, NULL);
      rc = opt->callback(fn, opt->callbackState);
      if(rc) goto end;
    }
    fsl_stmt_finalize(&q);
  }
  /* Remove rm'd ADDed-but-not-yet-committed entries... */
  rc = fsl_db_exec(db,
                   "DELETE FROM vfile WHERE vid=%" FSL_ID_T_PFMT
                   " AND rid=0 AND deleted",
                   vid);
  if(rc) goto dberr;
  end:
  if(fname) fsl_cx_scratchpad_yield(f, fname);
  fsl_stmt_finalize(&q);
  if(opt->vfileIds){
    fsl_db_exec(db, "DROP TABLE IF EXISTS fx_unmanage_id /* %s() */",
                __func__)
      /* Ignoring result code */;
  }
  if(inTrans){
    int const rc2 = fsl_db_transaction_end(db, !!rc);
    if(!rc) rc = rc2;
  }
  return rc;
  dberr:
  assert(rc);
  rc = fsl_cx_uplift_db_error2(f, db, rc);
  goto end;

}

int fsl_ckout_changes_scan(fsl_cx * f){
  return fsl_vfile_changes_scan(f, -1, 0);
}

int fsl_ckout_install_schema(fsl_cx *f, bool dropIfExists){
  char const * tNames[] = {
  "vvar", "vfile", "vmerge", 0
  };
  int rc;
  fsl_db * const db = fsl_needs_ckout(f);
  if(!db) return f->error.code;
  if(dropIfExists){
    char const * t;
    int i;
    char const * dbName = fsl_db_role_label(FSL_DBROLE_CKOUT);
    for(i=0; 0!=(t = tNames[i]); ++i){
      rc = fsl_db_exec(db, "DROP TABLE IF EXISTS %s.%s /*%s()*/",
                       dbName, t, __func__);
      if(rc) break;
    }
    if(!rc){
      rc = fsl_db_exec(db, "DROP TRIGGER IF EXISTS "
                       "%s.vmerge_ck1 /*%s()*/",
                       dbName, __func__);
    }
  }else{
    if(fsl_db_table_exists(db, FSL_DBROLE_CKOUT,
                           tNames[0])){
      return 0;
    }
  }
  rc = fsl_db_exec_multi(db, "%s", fsl_schema_ckout());
  return fsl_cx_uplift_db_error2(f, db, rc);
}

bool fsl_ckout_has_changes(fsl_cx *f){
  fsl_db * const db = fsl_cx_db_ckout(f);
  if(!db) return false;
  return fsl_db_exists(db,
                       "SELECT 1 FROM vfile WHERE chnged "
                       "OR coalesce(origname != pathname, 0) "
                       "/*%s()*/", __func__)
    || fsl_db_exists(db,"SELECT 1 FROM vmerge /*%s()*/", __func__);
}

int fsl_ckout_clear_merge_state( fsl_cx *f ){
  fsl_db * const d = fsl_needs_ckout(f);
  int rc;
  if(d){
    rc = fsl_db_exec(d,"DELETE FROM vmerge /*%s()*/", __func__);
    rc = fsl_cx_uplift_db_error2(f, d, rc);
  }else{
    rc = FSL_RC_NOT_A_CKOUT;
  }
  return rc;
}

int fsl_ckout_clear_db(fsl_cx *f){
  fsl_db * const db = fsl_needs_ckout(f);
  if(!db) return f->error.code;
  return fsl_db_exec_multi(db,
                           "DELETE FROM vfile;"
                           "DELETE FROM vmerge;"
                           "DELETE FROM vvar WHERE name IN"
                           "('checkout','checkout-hash') "
                           "/*%s()*/", __func__);
}

fsl_db * fsl_cx_db_for_role(fsl_cx *, fsl_dbrole_e)
  /* defined in cx.c */;

/**
   Updates f->ckout.dir and dirLen based on the current state of
   f->ckout.db. Returns 0 on success, FSL_RC_OOM on allocation error,
   some other code if canonicalization of the name fails
   (e.g. filesystem error or cwd cannot be resolved).
*/
static int fsl_update_ckout_dir(fsl_cx *f){
  int rc;
  fsl_buffer ckDir = fsl_buffer_empty;
  fsl_db * dbC = fsl_cx_db_for_role(f, FSL_DBROLE_CKOUT);
  assert(dbC->filename);
  assert(*dbC->filename);
  rc = fsl_file_canonical_name(dbC->filename, &ckDir, false);
  if(rc) return rc;
  char * zCanon = fsl_buffer_take(&ckDir);
  //MARKER(("dbC->filename=%s\n", dbC->filename));
  //MARKER(("zCanon=%s\n", zCanon));
  rc = fsl_file_dirpart(zCanon, -1, &ckDir, true);
  fsl_free(zCanon);
  if(rc){
    fsl_buffer_clear(&ckDir);
  }else{
    fsl_free(f->ckout.dir);
    f->ckout.dirLen = ckDir.used;
    f->ckout.dir = fsl_buffer_take(&ckDir);
    assert('/'==f->ckout.dir[f->ckout.dirLen-1]);
    /*MARKER(("Updated ckout.dir: %d %s\n",
      (int)f->ckout.dirLen, f->ckout.dir));*/
  }
  return rc;
}


int fsl_repo_open_ckout(fsl_cx *f, const fsl_repo_open_ckout_opt *opt){
  fsl_db *dbC = 0;
  fsl_buffer *cwd = 0;
  int rc = 0;

  if(!opt) return FSL_RC_MISUSE;
  else if(!fsl_needs_repo(f)){
    return f->error.code;
  }else if(fsl_cx_db_ckout(f)){
    return fsl_cx_err_set(f, FSL_RC_MISUSE,
                          "A checkout is already attached.");
  }
  if(opt->targetDir && *opt->targetDir){
    if(fsl_chdir(opt->targetDir)){
      return fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                            "Directory not found or inaccessible: %s",
                            opt->targetDir);
    }
  }
  cwd = fsl_cx_scratchpad(f);
  assert(!cwd->used);
  if((rc = fsl_cx_getcwd(f, cwd))){
    assert(!cwd->used);
    fsl_cx_scratchpad_yield(f, cwd);
    return fsl_cx_err_set(f, rc, "Error %d [%s]: unable to "
                          "determine current directory.",
                          rc, fsl_rc_cstr(rc));
  }
  /**
     AS OF HERE: do not use 'return'. Use goto end so that we can
     chdir() back to our original cwd!
  */
  if(!fsl_dir_is_empty("."/*we've already chdir'd if
                            we were going to*/)) {
    switch(opt->fileOverwritePolicy){
      case FSL_OVERWRITE_ALWAYS:
      case FSL_OVERWRITE_NEVER: break;
      default:
        assert(FSL_OVERWRITE_ERROR==opt->fileOverwritePolicy);
        rc = fsl_cx_err_set(f, FSL_RC_ACCESS,
                            "Directory is not empty and "
                            "fileOverwritePolicy is "
                            "FSL_OVERWRITE_ERROR: "
                            "%b", cwd);
        goto end;
    }
  }
  if(opt->checkForOpenedCkout){
    /* Check target and parent dirs for a checkout and bail out if we
       find one. If opt->checkForOpenedCkout is false then we will use
       the dbOverwritePolicy to determine what to do if we find a
       checkout db in cwd (as opposed to a parent). */
    fsl_buffer * const foundAt = fsl_cx_scratchpad(f);
    if (!fsl_ckout_db_search(fsl_buffer_cstr(cwd), true, foundAt)) {
      rc = fsl_cx_err_set(f, FSL_RC_ALREADY_EXISTS,
                          "There is already a checkout db at %b",
                          foundAt);
    }
    fsl_cx_scratchpad_yield(f, foundAt);
    if(rc) goto end;
  }

  /**
     Create and attach ckout db...
  */
  assert(!fsl_cx_db_ckout(f));
  const char * dbName = opt->ckoutDbFile
    ? opt->ckoutDbFile : fsl_preferred_ckout_db_name();
  fsl_cx_err_reset(f);
  int fsl_cx_attach_role(fsl_cx *, const char *, fsl_dbrole_e)
    /* defined in cx.c */;
  rc = fsl_cx_attach_role(f, dbName, FSL_DBROLE_CKOUT);
  if(rc) goto end;
  fsl_db * const theDbC = fsl_cx_db_ckout(f);
  dbC = fsl_cx_db_for_role(f, FSL_DBROLE_CKOUT);
  assert(theDbC != dbC && "Not anymore.");
  assert(theDbC == f->dbMain);
  assert(!f->error.code);
  assert(dbC->name);
  assert(dbC->filename);
  rc = fsl_ckout_install_schema(f, opt->dbOverwritePolicy);
  if(!rc){
    rc = fsl_db_exec(theDbC,"INSERT OR IGNORE INTO "
                     "%s.vvar (name,value) "
                     "VALUES('checkout',0),"
                     "('checkout-hash',null)",
                     dbC->name);
  }
  if(rc) rc = fsl_cx_uplift_db_error(f, theDbC);
  end:
  if(opt->targetDir && *opt->targetDir && cwd->used){
    fsl_chdir(fsl_buffer_cstr(cwd))
      /* Ignoring error because we have no recovery strategy! */;
  }
  fsl_cx_scratchpad_yield(f, cwd);
  if(!rc){
    fsl_db * const dbR = fsl_cx_db_for_role(f, FSL_DBROLE_REPO);
    assert(dbR);
    assert(dbR->filename && *dbR->filename);
    rc = fsl_config_set_text(f, FSL_CONFDB_CKOUT, "repository",
                             dbR->filename);
  }
  if(!rc) rc = fsl_update_ckout_dir(f);
  fsl_buffer_clear(cwd);
  return rc;
}

int fsl_is_locally_modified(fsl_cx * f, const char * zFilename,
                            fsl_size_t origSize,
                            const char * zOrigHash,
                            fsl_int_t zOrigHashLen,
                            fsl_fileperm_e origPerm,
                            int * isModified){
  int rc = 0;
  int const hashLen = zOrigHashLen>=0
    ? zOrigHashLen : fsl_is_uuid(zOrigHash);
  fsl_buffer * hash = 0;
  fsl_buffer * fname = fsl_cx_scratchpad(f);
  fsl_fstat * const fst = &f->cache.fstat;
  int mod = 0;
  if(!fsl_is_uuid_len(hashLen)){
    return fsl_cx_err_set(f, FSL_RC_RANGE, "%s(): invalid hash length "
                          "%d for file: %s", __func__, hashLen, zFilename);
  }else if(!f->ckout.dir){
    return fsl_cx_err_set(f, FSL_RC_NOT_A_CKOUT,
                          "%s() requires a checkout.", __func__);
  }
  if(!fsl_is_absolute_path(zFilename)){
    rc = fsl_file_canonical_name2(f->ckout.dir, zFilename, fname, false);
    if(rc) goto end;
    zFilename = fsl_buffer_cstr(fname);
  }
  rc = fsl_stat(zFilename, fst, false);
  if(0==rc){
    if(origSize!=fst->size){
      mod |= 0x02;
    }
    if((FSL_FILE_PERM_EXE==origPerm &&
        FSL_FSTAT_PERM_EXE!=fst->perm)
       || (FSL_FILE_PERM_EXE!=origPerm &&
           FSL_FSTAT_PERM_EXE==fst->perm)){
      mod |= 0x01;
    }else if((FSL_FILE_PERM_LINK==origPerm &&
              FSL_FSTAT_TYPE_LINK!=fst->type)
             || (FSL_FILE_PERM_LINK!=origPerm &&
                 FSL_FSTAT_TYPE_LINK==fst->type)){
      mod |= 0x04;
    }
    if(mod & 0x06) goto end;
    /* ^^^^^^^^^^ else we unfortunately need, for behavioral
       consistency, to fall through and determine whether the file
       contents differ. */
  }else{
    if(FSL_RC_NOT_FOUND==rc){
      rc = 0;
      mod = 0x10;
    }else{
      rc = fsl_cx_err_set(f, rc, "%s(): stat() failed for file: %s",
                          __func__, zFilename);
    }
    goto end;
  }
  hash = fsl_cx_scratchpad(f);
  switch(hashLen){
    case FSL_STRLEN_SHA1:
      rc = fsl_sha1sum_filename(zFilename, hash);
      break;
    case FSL_STRLEN_K256:
      rc = fsl_sha3sum_filename(zFilename, hash);
      break;
    default:
      fsl_fatal(FSL_RC_UNSUPPORTED, "This cannot happen. %s()",
                __func__);
  }
  if(rc){
    rc = fsl_cx_err_set(f, rc, "%s: error hashing file: %s",
                        __func__, zFilename);
  }else{
    assert(hashLen==(int)hash->used);
    mod |= memcmp(hash->mem, zOrigHash, (size_t)hashLen)
      ? 0x02 : 0;
    /*MARKER(("%d: %s %s %s\n", *isModified, zOrigHash,
      (char const *)hash.mem, zFilename));*/
  }
  end:
  if(!rc && isModified) *isModified = mod;
  fsl_cx_scratchpad_yield(f, fname);
  if(hash) fsl_cx_scratchpad_yield(f, hash);
  return rc;
}

/**
   Infrastructure for fsl_repo_ckout() and
   fsl_ckout_update().
*/
typedef struct {
  /** The pre-checkout vfile.vid. 0 if no version was
      checked out. */
  fsl_id_t originRid;
  fsl_repo_extract_opt const * eOpt;
  fsl_ckup_opt const * cOpt;
  /* Checkout root. We re-use this when internally converting to
     absolute paths. */
  fsl_buffer * tgtDir;
  /* Initial length of this->tgtDir, including trailing slash */
  fsl_size_t tgtDirLen;
  /* Number of files we've written out so far. Used for adapting
     some error reporting. */
  fsl_size_t fileWriteCount;
  /* Stores the most recent fsl_cx_confirm() answer for questions
     about overwriting/removing modified files. (Exactly which answer
     it represents depends on the current phase of processing.)
  */
  fsl_confirm_response confirmAnswer;
  /* Is-changed vis-a-vis vfile query. */
  fsl_stmt stChanged;
  /* Is-same-filename-and-rid-in-vfile query. */
  fsl_stmt stIsInVfile;
  /* blob.size for vfile.rid query. */
  fsl_stmt stRidSize;
} RepoExtractCkup;

static const RepoExtractCkup RepoExtractCkup_empty = {
0/*originRid*/,NULL/*eOpt*/, NULL/*cOpt*/,
NULL/*tgtDir*/, 0/*tgtDirLen*/,
0/*fileWriteCount*/,
fsl_confirm_response_empty_m/*confirmAnswer*/,
fsl_stmt_empty_m/*stChanged*/,
fsl_stmt_empty_m/*stIsInVfile*/,
fsl_stmt_empty_m/*stRidSize*/
};

static const fsl_ckup_state fsl_ckup_state_empty = {
NULL/*xState*/, NULL/*callbackState*/,
FSL_CKUP_FCHANGE_INVALID/*fileChangeType*/,
FSL_CKUP_RM_NOT/*fileRmInfo*/,
0/*mtime*/,0/*size*/,
false/*dryRun*/
};

/**
   File modification types reported by
   fsl_reco_is_file_modified().
 */
typedef enum {
// Sentinel value
FSL_RECO_MOD_UNKNOWN,
// Not modified
FSL_RECO_MOD_NO,
// Modified
FSL_RECO_MOD_YES,
// "Unmanaged replaced by managed"
FSL_RECO_MOD_UnReMa
} fsl_ckup_localmod_e;

/**
   Determines whether the file referred to by the given
   checkout-root-relative file name, which is assumed to be known to
   exist, has been modified. It simply looks to the vfile state,
   rather than doing its own filesystem-level comparison. Returns 0 on
   success and stores its answer in *modType. Errors must be
   considered unrecoverable.
*/
static int fsl_reco_is_file_modified(fsl_cx *f, fsl_stmt * st,
                                     char const *zName,
                                     fsl_ckup_localmod_e * modType){
  int rc = 0;
  if(!st->stmt){ // no prior version
    *modType = FSL_RECO_MOD_NO;
    return 0;
  }
  fsl_stmt_reset(st);
  rc = fsl_stmt_bind_text(st, 1, zName, -1, false);
  if(rc){
    return fsl_cx_uplift_db_error2(f, st->db, rc);
  }
  rc = fsl_stmt_step(st);
  switch(rc){
    case FSL_RC_STEP_DONE:
      /* This can happen when navigating from a version in which a
         file was SCM-removed/unmanaged, but on disk, to a version
         where that file was in SCM. For now we'll mark these as
         modified but we need a better way of handling this case, and
         maybe a new FSL_CEVENT_xxx ID. */
      *modType = FSL_RECO_MOD_UnReMa;
      rc = 0;
      break;
    case FSL_RC_STEP_ROW:
      *modType = fsl_stmt_g_int32(st,0)>0
        ? FSL_RECO_MOD_YES : FSL_RECO_MOD_NO;
      rc = 0;
      break;
    default:
      rc = fsl_cx_uplift_db_error2(f, st->db, rc);
      break;
  }
  return rc;
}

/**
   Sets *isInVfile to true if the given combination of filename and
   file content RID are in the vfile table, as per
   RepoExtractCkup::stIsInVfile, else false. Returns non-0 on
   catastrophic failure.
*/
static int fsl_repo_co_is_in_vfile(fsl_stmt * st,
                                   char const *zFilename,
                                   fsl_id_t fileRid,
                                   bool *isInVfile){
  int rc = 0;
  if(st->stmt){
    fsl_stmt_reset(st);
    rc = fsl_stmt_bind_text(st, 1, zFilename, -1, false);
    if(!rc) rc = fsl_stmt_bind_id(st, 2, fileRid);
    if(!rc) *isInVfile = (FSL_RC_STEP_ROW==fsl_stmt_step(st));
  }else{ // no prior version
    *isInVfile = false;
  }
  return rc;
}

/**
   Infrastructure for fsl_repo_ckout(). This is the fsl_repo_extract_f
   impl which fsl_repo_extract() calls to give us the pieces we want to
   check out.

   When this is run (once for each row of the new checkout version),
   the vfile table still holds the state for the previous version, and
   we use that to determine whether a file is changed or new.
*/
static int fsl_repo_extract_f_ckout( fsl_repo_extract_state const * xs ){
  int rc = 0;
  fsl_cx * const f = xs->f;
  RepoExtractCkup * const rec = (RepoExtractCkup *)xs->callbackState;
  const char * zFilename;
  fsl_ckup_state coState = fsl_ckup_state_empty;
  fsl_time_t mtime = 0;
  fsl_fstat fst = fsl_fstat_empty;
  fsl_ckup_localmod_e modType = FSL_RECO_MOD_UNKNOWN;
  bool loadedContent = false;
  fsl_buffer * content = &f->fileContent;
  assert(0==content->used
         && "Internal Misuse of fsl_cx::fileContent buffer.");
  //assert(xs->content);
  assert(xs->fCard->uuid && "We shouldn't be getting deletions "
         "via delta manifests.");
  rc = fsl_buffer_append(rec->tgtDir, xs->fCard->name, -1);
  if(rc) return rc;
  fsl_buffer_reuse(content);
  coState.dryRun = rec->cOpt->dryRun;
  coState.fileRmInfo = FSL_CKUP_RM_NOT;
  coState.fileChangeType = FSL_CKUP_FCHANGE_INVALID;
  zFilename = fsl_buffer_cstr(rec->tgtDir);
  rc = fsl_stat(zFilename, &fst, 0);
  switch(rc){
    case 0:
      /* File exists. If it is modified, as reported by vfile, get
         confirmation before overwriting it, otherwise just overwrite
         it (or keep it - that's much more efficient). */
      mtime = fst.mtime;
      if(rec->confirmAnswer.response!=FSL_CRESPONSE_ALWAYS){
        rc = fsl_reco_is_file_modified(f, &rec->stChanged,
                                       xs->fCard->name, &modType);
        if(rc) goto end;
        switch(modType){
          case FSL_RECO_MOD_YES:
          case FSL_RECO_MOD_UnReMa:
            if(rec->confirmAnswer.response!=FSL_CRESPONSE_NEVER){
              fsl_confirm_detail detail = fsl_confirm_detail_empty;
              detail.eventId = FSL_RECO_MOD_YES==modType
                ? FSL_CEVENT_OVERWRITE_MOD_FILE
                : FSL_CEVENT_OVERWRITE_UNMGD_FILE;
              detail.filename = xs->fCard->name;
              rec->confirmAnswer.response = FSL_CRESPONSE_INVALID;
              rc = fsl_cx_confirm(f, &detail, &rec->confirmAnswer);
              if(rc) goto end;
            }
            break;
          case FSL_RECO_MOD_NO:{
            /** If vfile says that the content of this exact
                combination of filename and file RID is unchanged, we
                already have this content. If so, skip rewriting
                it. */
            bool isSameFile = false;
            rc = fsl_repo_co_is_in_vfile(&rec->stIsInVfile, xs->fCard->name,
                                         xs->fileRid, &isSameFile);
            if(rc) goto end;
            rec->confirmAnswer.response = isSameFile
              ? FSL_CRESPONSE_NO // We already have this content
              : FSL_CRESPONSE_YES; // Overwrite it
            coState.fileChangeType = isSameFile
              ? FSL_CKUP_FCHANGE_NONE
              : FSL_CKUP_FCHANGE_UPDATED;
            break;
          }
          default:
            fsl_fatal(FSL_RC_UNSUPPORTED,"Internal error: invalid "
                      "fsl_reco_is_file_modified() response.");
        }
      }
      switch(rec->confirmAnswer.response){
        case FSL_CRESPONSE_NO:
        case FSL_CRESPONSE_NEVER:
          // Keep existing.
          coState.fileChangeType = FSL_CKUP_FCHANGE_NONE;
          goto do_callback;
        case FSL_CRESPONSE_YES:
        case FSL_CRESPONSE_ALWAYS:
          // Overwrite it.
          coState.fileChangeType = FSL_CKUP_FCHANGE_UPDATED;
          break;
        case FSL_CRESPONSE_CANCEL:
          rc = fsl_cx_err_set(f, FSL_RC_BREAK,
                              "Checkout operation cancelled by "
                              "confirmation callback.%s",
                              rec->fileWriteCount
                              ? " Filesystem contents may now be "
                                "in an inconsistent state!"
                              : "");
          goto end;
        default:
          rc = fsl_cx_err_set(f, FSL_RC_MISUSE,
                              "Invalid response from confirmation "
                              "callback.");
          goto end;
      }
      break;
    case FSL_RC_NOT_FOUND:
      rc = 0;
      coState.fileChangeType = FSL_CKUP_FCHANGE_UPDATED;
      // Write it
      break;
    default:
      rc = fsl_cx_err_set(f, rc, "Error %s stat()'ing file: %s",
                          fsl_rc_cstr(rc), zFilename);
      goto end;
  }
  assert(FSL_CKUP_FCHANGE_INVALID != coState.fileChangeType);
  if(coState.dryRun){
    mtime = time(0);
  }else{
    if((rc=fsl_mkdir_for_file(zFilename, true))){
      rc = fsl_cx_err_set(f, rc, "mkdir() failed for file: %s", zFilename);
      goto end;
    }
    assert(!xs->content);
    rc = fsl_card_F_content(f, xs->fCard, content);
    if(rc) goto end;
    else if((rc=fsl_buffer_to_filename(content, zFilename))){
      rc = fsl_cx_err_set(f, rc, "Error %s writing to file: %s",
                          fsl_rc_cstr(rc), zFilename);
      goto end;
    }else{
      loadedContent = true;
      ++rec->fileWriteCount;
      mtime = time(0);
    }
    rc = fsl_file_exec_set(zFilename,
                           FSL_FILE_PERM_EXE == xs->fCard->perm);
    if(rc){
      rc = fsl_cx_err_set(f, rc, "Error %s changing file permissions: %s",
                          fsl_rc_cstr(rc), xs->fCard->name);
      goto end;
    }
  }
  if(rec->cOpt->setMtime){
    rc = fsl_mtime_of_manifest_file(xs->f, xs->checkinRid,
                                    xs->fileRid, &mtime);
    if(rc) goto end;
    if(!coState.dryRun){
      rc = fsl_file_mtime_set(zFilename, mtime);
      if(rc){
        rc = fsl_cx_err_set(f, rc, "Error %s setting mtime of file: %s",
                            fsl_rc_cstr(rc), zFilename);
        goto end;
      }
    }
  }
  do_callback:
  assert(0==rc);
  if(rec->cOpt->callback){
    assert(mtime);
    coState.mtime = mtime;
    coState.extractState = xs;
    coState.callbackState = rec->cOpt->callbackState;
    if(loadedContent){
      coState.size = content->used;
    }else{
      fsl_stmt_reset(&rec->stRidSize);
      fsl_stmt_bind_id(&rec->stRidSize, 1, xs->fileRid);
      coState.size =
        (FSL_RC_STEP_ROW==fsl_stmt_step(&rec->stRidSize))
        ? (fsl_int_t)fsl_stmt_g_int64(&rec->stRidSize, 0)
        : -1;
    }
    rc = rec->cOpt->callback( &coState );
  }
  end:
  fsl_buffer_reuse(content);
  rec->tgtDir->used = rec->tgtDirLen;
  rec->tgtDir->mem[rec->tgtDirLen] = 0;
  return rc;
}

/**
   For each file in vfile(vid=rec->originRid) which is not in the
   current vfile(vid=rec->cOpt->checkinRid), remove it from disk (or
   not, depending on confirmer response). Afterwards, try to remove
   any dangling directories left by that removal.

   Returns 0 on success. Ignores any filesystem-level errors during
   removal because, frankly, we have no recovery strategy for that
   case.

   TODO: do not remove dirs from the 'empty-dirs' config setting.
*/
static int fsl_repo_ckout_rm_list_fini(fsl_cx * f,
                                       RepoExtractCkup * rec){
  int rc;
  fsl_db * db = fsl_cx_db_ckout(f);
  fsl_stmt q = fsl_stmt_empty;
  fsl_buffer * absPath = fsl_cx_scratchpad(f);
  fsl_size_t const ckdirLen = f->ckout.dirLen;
  char const *zAbs;
  int rmCounter = 0;
  fsl_ckup_opt const * cOpt = rec->cOpt;
  fsl_ckup_state cuState = fsl_ckup_state_empty;
  fsl_repo_extract_state rxState = fsl_repo_extract_state_empty;
  fsl_card_F fCard = fsl_card_F_empty;
  
  assert(db);
  rc = fsl_buffer_append(absPath, f->ckout.dir,
                         (fsl_int_t)f->ckout.dirLen);
  if(rc) goto end;
  /* Select files which were in the previous version
     (rec->originRid) but are not in the newly co'd version
     (cOpt->checkinRid). */
  rc = fsl_db_prepare(db, &q,
                      "SELECT "
                      /*0*/"v.rid frid,"
                      /*1*/"v.pathname fn,"
                      /*2*/"b.uuid,"
                      /*3*/"v.isexe,"
                      /*4*/"v.islink,"
                      /*5*/"v.chnged, "
                      /*6*/"b.size "
                      "FROM vfile v, blob b "
                      "WHERE v.vid=%" FSL_ID_T_PFMT " "
                      "AND v.rid=b.rid "
                      "AND fn NOT IN "
                      "(SELECT pathname FROM vfile "
                      " WHERE vid=%" FSL_ID_T_PFMT
                      ") "
                      "ORDER BY fn %s /*%s()*/",
                      rec->originRid,
                      cOpt->checkinRid
                      /*new checkout version resp. update target
                        version*/,
                      fsl_cx_filename_collation(f),
                      __func__);
  if(rc) goto end;

  rec->confirmAnswer.response = FSL_CRESPONSE_INVALID;
  cuState.mtime = 0;
  cuState.size = -1;
  cuState.callbackState = cOpt->callbackState;
  cuState.extractState = &rxState;
  cuState.dryRun = cOpt->dryRun;
  cuState.fileChangeType = FSL_CKUP_FCHANGE_RM;
  rxState.f = f;
  rxState.fCard = &fCard;
  rxState.checkinRid = cOpt->checkinRid;
  while(FSL_RC_STEP_ROW==(rc = fsl_stmt_step(&q))){
    /**
       Each row is one file listed in vfile (the old checkout
       version) which is not in vfile (the new checkout).
    */
    fsl_size_t nFn = 0;
    fsl_size_t hashLen = 0;
    char const * fn = fsl_stmt_g_text(&q, 1, &nFn);
    char const * hash = fsl_stmt_g_text(&q, 2, &hashLen);
    bool const isChanged = fsl_stmt_g_int32(&q, 5)!=0;
    int64_t const fSize = fsl_stmt_g_int64(&q, 6);
    if(FSL_CRESPONSE_ALWAYS!=rec->confirmAnswer.response){
      /**
         If the user has previously responded to
         FSL_CEVENT_RM_MOD_UNMGD_FILE, keep that response, else
         ask again if the file was flagged as changed in the
         vfile table before all of this started.
      */
      if(isChanged){
        // Modified: ask user unless they've already answered NEVER.
        if(FSL_CRESPONSE_NEVER!=rec->confirmAnswer.response){
          fsl_confirm_detail detail = fsl_confirm_detail_empty;
          detail.eventId = FSL_CEVENT_RM_MOD_UNMGD_FILE;
          detail.filename = fn;
          rec->confirmAnswer.response = FSL_CRESPONSE_INVALID;
          rc = fsl_cx_confirm(f, &detail, &rec->confirmAnswer);
          if(rc) goto end;
        }
      }else{
        // Not modified. Nuke it.
        rec->confirmAnswer.response = FSL_CRESPONSE_YES;
      }
    }
    absPath->used = ckdirLen;
    rc = fsl_buffer_append(absPath, fn, nFn);
    if(rc) break;
    zAbs = fsl_buffer_cstr(absPath);
    /* Ignore deletion errors. We cannot roll back previous deletions,
       so failing here, which would roll back the transaction, could
       leave the checkout in a weird state, potentially with some
       files missing and others not. */
    switch(rec->confirmAnswer.response){
      case FSL_CRESPONSE_YES:
      case FSL_CRESPONSE_ALWAYS:
        //MARKER(("Unlinking: %s\n",zAbs));
        if(!cOpt->dryRun && 0==fsl_file_unlink(zAbs)){
          ++rmCounter;
        }
        cuState.fileRmInfo = FSL_CKUP_RM;
        break;
      case FSL_CRESPONSE_NO:
      case FSL_CRESPONSE_NEVER:
        //assert(FSL_RECO_MOD_YES==modType);
        //MARKER(("NOT removing locally-modified file: %s\n", zN));
        cuState.fileRmInfo = FSL_CKUP_RM_KEPT;
        break;
      case FSL_CRESPONSE_CANCEL:
        rc = fsl_cx_err_set(f, FSL_RC_BREAK,
                            "Checkout operation cancelled by "
                            "confirmation callback. "
                            "Filesystem contents may now be "
                            "in an inconsistent state!");
        goto end;
      default:
        fsl_fatal(FSL_RC_UNSUPPORTED,"Internal error: invalid "
                  "fsl_cx_confirm() response #%d.",
                  rec->confirmAnswer.response);
        break;
    }
    if(!cOpt->callback) continue;
    /* Now report the deletion to the callback... */
    fsl_id_t const frid = fsl_stmt_g_id(&q, 0);
    const bool isExe = 0!=fsl_stmt_g_int32(&q, 3);
    const bool isLink = 0!=fsl_stmt_g_int32(&q, 4);
    cuState.size = (FSL_CKUP_RM==cuState.fileRmInfo) ? -1 : fSize;
    rxState.fileRid = frid;
    fCard = fsl_card_F_empty;
    fCard.name = (char *)fn;
    fCard.uuid = (char *)hash;
    fCard.perm = isExe ? FSL_FILE_PERM_EXE :
      (isLink ? FSL_FILE_PERM_LINK : FSL_FILE_PERM_REGULAR);
    rc = cOpt->callback( &cuState );
    if(rc) goto end;
  }
  if(FSL_RC_STEP_DONE==rc) rc = 0;
  else goto end;
  if(rmCounter>0){
    /* Clean up any empty directories left over by removal of
       files... */
    assert(!cOpt->dryRun);
    fsl_stmt_finalize(&q);
    /* Select dirs which were in the previous version
       (rec->originRid) but are not in the newly co'd version
       (cOpt->checkinRid). Any of these may _potentially_
       be empty now. This query could be improved to filter 
       out more in advance. */
    rc = fsl_db_prepare(db, &q,
                        "SELECT DISTINCT(fsl_dirpart(pathname,0)) dir "
                        "FROM vfile "
                        "WHERE vid=%" FSL_ID_T_PFMT " "
                        "AND pathname NOT IN "
                        "(SELECT pathname FROM vfile "
                        "WHERE vid=%" FSL_ID_T_PFMT ") "
                        "AND dir IS NOT NULL "
                        "ORDER BY length(dir) DESC /*%s()*/",
                        /*get deepest dirs first*/
                        rec->originRid, cOpt->checkinRid,
                        __func__);
    if(rc) goto end;
    while(FSL_RC_STEP_ROW==(rc = fsl_stmt_step(&q))){
      fsl_size_t nFn = 0;
      char const * fn = fsl_stmt_g_text(&q, 0, &nFn);
      absPath->used = ckdirLen;
      rc = fsl_buffer_append(absPath, fn, nFn);
      if(rc) break;
      fsl_ckout_rm_empty_dirs(f, absPath)
        /* To see this in action, use (f-co tip) to check out the tip of
           a repo, then use (f-co rid:1) to back up to the initial empty
           checkin. It "should" leave you with a directory devoid of
           anything but .fslckout and any non-SCM'd content.
        */;
    }
    if(FSL_RC_STEP_DONE==rc) rc = 0;
  }
  end:
  fsl_stmt_finalize(&q);
  fsl_cx_scratchpad_yield(f, absPath);
  return fsl_cx_uplift_db_error2(f, db, rc);
}

int fsl_repo_ckout(fsl_cx * f, fsl_ckup_opt const * cOpt){
  int rc = 0;
  fsl_id_t const prevRid = f->ckout.rid;
  fsl_db * const dbR = fsl_needs_repo(f);
  RepoExtractCkup rec = RepoExtractCkup_empty;
  fsl_confirmer oldConfirm = fsl_confirmer_empty;
  if(!dbR) return f->error.code;
  else if(!fsl_needs_ckout(f)) return f->error.code;
  rc = fsl_cx_transaction_begin(f);
  if(rc) return rc;
  rec.tgtDir = fsl_cx_scratchpad(f);
  if(cOpt->confirmer.callback){
    fsl_cx_confirmer(f, &cOpt->confirmer, &oldConfirm);
  }
  //MARKER(("ckout.rid=%d\n",(int)prevRid));
  if(prevRid>=0 && cOpt->scanForChanges){
    /* We need to ensure this state is current in order to determine
       whether a given file is locally modified vis-a-vis the
       pre-extract checkout state. */
    rc = fsl_vfile_changes_scan(f, prevRid, 0);
    if(rc) goto end;
  }
  if(0){
    fsl_db_each(dbR,fsl_stmt_each_f_dump, NULL,
                "SELECT * FROM vfile ORDER BY pathname");
  }
  assert(f->ckout.dirLen);
  fsl_repo_extract_opt eOpt = fsl_repo_extract_opt_empty;
  rc = fsl_buffer_append(rec.tgtDir, f->ckout.dir,
                         (fsl_int_t)f->ckout.dirLen);
  if(rc) goto end;
  if(prevRid){
    rc = fsl_db_prepare(dbR, &rec.stChanged,
                        "SELECT chnged FROM vfile "
                        "WHERE vid=%" FSL_ID_T_PFMT
                        " AND pathname=? %s",
                        prevRid,
                        fsl_cx_filename_collation(f));
  }
  if(!rc && prevRid){
    /* Optimization: before we load content for a blob and write it to
       a file, check this query for whether we already have the same
       name/rid combination in vfile, and skip loading/writing the
       content if we do. */
    rc = fsl_db_prepare(dbR, &rec.stIsInVfile,
                        "SELECT 1 FROM vfile "
                        "WHERE vid=%" FSL_ID_T_PFMT
                        " AND pathname=? AND rid=? %s",
                        prevRid, fsl_cx_filename_collation(f));
  }
  if(!rc){
    /* Files for which we don't load content (see rec.stIsInVfile)
       still have a size we need to report via fsl_ckup_state,
       and we fetch that with this query. */
    rc = fsl_db_prepare(dbR, &rec.stRidSize,
                        "SELECT size FROM blob WHERE rid=?");
  }
  if(rc){
    rc = fsl_cx_uplift_db_error2(f, dbR, rc);
    goto end;
  }
  rec.originRid = prevRid;
  rec.tgtDirLen = f->ckout.dirLen;
  eOpt.checkinRid = cOpt->checkinRid;
  eOpt.extractContent = false;
  eOpt.callbackState = &rec;
  eOpt.callback = fsl_repo_extract_f_ckout;
  rec.eOpt = &eOpt;
  rec.cOpt = cOpt;
  rc = fsl_repo_extract(f, &eOpt);
  if(!rc){
    /*
      We need to call fsl_vfile_load(f, cOpt->vid) to
      populate vfile but we also need to call
      fsl_vfile_changes_scan(f, cOpt->vid, 0) to set the vfile.mtime
      fields. The latter calls the former, so...
    */
    rc = fsl_vfile_changes_scan(f, cOpt->checkinRid,
                                FSL_VFILE_CKSIG_WRITE_CKOUT_VERSION
                                |
                                (prevRid==0
                                 ? 0 : FSL_VFILE_CKSIG_KEEP_OTHERS)
                                |
                                (cOpt->setMtime
                                 ? 0 : FSL_VFILE_CKSIG_SETMTIME)
                                /* Note that mtimes were set during
                                   extraction if cOpt->setMtime is
                                   true. */);
    if(rc) goto end;
    assert(f->ckout.rid==cOpt->checkinRid);
    assert(f->ckout.rid ? !!f->ckout.uuid : 1);
  }
  /**
    TODO: Implement db fingerprint.
  */
  if(!rc && prevRid!=0){
    rc = fsl_repo_ckout_rm_list_fini(f, &rec);
    if(rc) goto end;
  }
  rc = fsl_ckout_manifest_write(f, -1, -1, -1, NULL);
  end:
  if(!rc){
    rc = fsl_vfile_unload_except(f, cOpt->checkinRid);
    if(!rc) rc = fsl_ckout_clear_merge_state(f);
  }
  if(cOpt->confirmer.callback){
    fsl_cx_confirmer(f, &oldConfirm, NULL);
  }
  fsl_stmt_finalize(&rec.stChanged);
  fsl_stmt_finalize(&rec.stIsInVfile);
  fsl_stmt_finalize(&rec.stRidSize);
  fsl_cx_scratchpad_yield(f, rec.tgtDir);
  int const rc2 = fsl_cx_transaction_end(f, rc || cOpt->dryRun);
  return rc ? rc : rc2;
}

int fsl_ckout_update(fsl_cx * f, fsl_ckup_opt const *cuOpt){
  fsl_db * const dbR = fsl_needs_repo(f);
  fsl_db * const dbC = dbR ? fsl_needs_ckout(f) : 0;
  if(!dbR) return FSL_RC_NOT_A_REPO;
  else if(!dbC) return FSL_RC_NOT_A_CKOUT;
  int rc = 0, rc2 = 0;
  char const * collation = fsl_cx_filename_collation(f);
  fsl_id_t const ckRid = f->ckout.rid /* current version */;
  fsl_id_t const tid = cuOpt->checkinRid /* target version */;
  fsl_stmt q = fsl_stmt_empty;
  fsl_stmt mtimeXfer = fsl_stmt_empty;
  fsl_stmt mtimeGet = fsl_stmt_empty;
  fsl_stmt mtimeSet = fsl_stmt_empty;
  fsl_buffer * bFullPath = 0;
  fsl_buffer * bFullNewPath = 0;
  fsl_buffer * bFileUuid = 0;
  fsl_repo_extract_opt eOpt = fsl_repo_extract_opt_empty
    /* We won't actually use fsl_repo_extract() here because it's a
       poor fit for the update selection algorithm, but in order to
       consolidate some code between the ckout/update cases we need to
       behave as if we were using it. */;
  fsl_repo_extract_state xState = fsl_repo_extract_state_empty;
  fsl_card_F fCard = fsl_card_F_empty;
  fsl_ckup_state uState = fsl_ckup_state_empty;
  RepoExtractCkup rec = RepoExtractCkup_empty;
  enum { MergeBufCount = 4 };
  fsl_buffer bufMerge[MergeBufCount] = {
    fsl_buffer_empty_m/* pivot: ridv */,
    fsl_buffer_empty_m/* local file to merge into */,
    fsl_buffer_empty_m/* update-to: ridt */,
    fsl_buffer_empty_m/* merged copy */
  };

  rc = fsl_db_transaction_begin(dbC);
  if(rc) return fsl_cx_uplift_db_error2(f, dbC, rc);
  if(cuOpt->scanForChanges){
    rc = fsl_vfile_changes_scan(f, ckRid, FSL_VFILE_CKSIG_ENOTFILE);
    if(rc) goto end;
  }
  if(tid != ckRid){
    uint32_t missingCount = 0;
    rc = fsl_vfile_load(f, tid, false,
                                 &missingCount);
    if(rc) goto end;
    else if(missingCount/* && !forceMissing*/){
      rc = fsl_cx_err_set(f, FSL_RC_PHANTOM,
                          "Unable to update due to missing content in "
                          "%"PRIu32" blob(s).", missingCount);
      goto end;
    }
  }
  /*
  ** The record.fn field is used to match files against each other.  The
  ** FV table contains one row for each each unique filename in
  ** in the current checkout, the pivot, and the version being merged.
  */
  rc = fsl_db_exec_multi(dbC,
    "DROP TABLE IF EXISTS fv;"
    "CREATE TEMP TABLE fv("
    "  fn TEXT %s PRIMARY KEY,"   /* The filename relative to root */
    "  idv INTEGER,"              /* VFILE entry for current version */
    "  idt INTEGER,"              /* VFILE entry for target version */
    "  chnged BOOLEAN,"           /* True if current version has been edited */
    "  islinkv BOOLEAN,"          /* True if current file is a link */
    "  islinkt BOOLEAN,"          /* True if target file is a link */
    "  ridv INTEGER,"             /* Record ID for current version */
    "  ridt INTEGER,"             /* Record ID for target */
    "  isexe BOOLEAN,"            /* Does target have execute permission? */
    "  deleted BOOLEAN DEFAULT 0,"/* File marked by "rm" to become unmanaged */
    "  fnt TEXT %s"               /* Filename of same file on target version */
    ") /*%s()*/;",
    collation, collation, __func__ );
  if(rc) goto dberr;
  /* Add files found in the current version
  */
  rc = fsl_db_exec_multi(dbC,
    "INSERT OR IGNORE INTO fv("
            "fn,fnt,idv,idt,ridv,"
            "ridt,isexe,chnged,deleted"
    ") SELECT pathname, pathname, id, 0, rid, 0, "
       "isexe, chnged, deleted "
       "FROM vfile WHERE vid=%" FSL_ID_T_PFMT
       "/*%s()*/",
    ckRid, __func__
  );
  if(rc) goto dberr;

  /* Compute file name changes on V->T.  Record name changes in files that
  ** have changed locally.
  */
  if( ckRid ){
    uint32_t nChng = 0;
    fsl_id_t * aChng = 0;
    rc = fsl_cx_find_filename_changes(f, ckRid, tid,
                                      true, &nChng, &aChng);
    if(rc){
      assert(!aChng);
      assert(!nChng);
      goto end;
    }
    if( nChng ){
      for(uint32_t i=0; i<nChng; ++i){
        rc = fsl_db_exec_multi(dbC,
          "UPDATE fv"
          "   SET fnt=(SELECT name FROM filename WHERE fnid=%"
              FSL_ID_T_PFMT ")"
          " WHERE fn=(SELECT name FROM filename WHERE fnid=%"
            FSL_ID_T_PFMT ") AND chnged /*%s()*/",
          aChng[i*2+1], aChng[i*2], __func__
        );
        if(rc) goto dberr;
      }
      fsl_free(aChng);
    }else{
      assert(!aChng);
    }
  }/*ckRid!=0*/

  /* Add files found in the target version T but missing from the current
  ** version V.
  */
  rc = fsl_db_exec_multi(dbC,
    "INSERT OR IGNORE INTO fv(fn,fnt,idv,idt,ridv,ridt,isexe,chnged)"
    " SELECT pathname, pathname, 0, 0, 0, 0, isexe, 0 FROM vfile"
    "  WHERE vid=%" FSL_ID_T_PFMT
    "    AND pathname %s NOT IN (SELECT fnt FROM fv) /*%s()*/",
    tid, collation, __func__
  );
  if(rc) goto dberr;

  /*
  ** Compute the file version ids for T
  */
  rc = fsl_db_exec_multi(dbC,
    "UPDATE fv SET"
    " idt=coalesce((SELECT id FROM vfile WHERE vid=%"
                   FSL_ID_T_PFMT " AND fnt=pathname),0),"
    " ridt=coalesce((SELECT rid FROM vfile WHERE vid=%"
                    FSL_ID_T_PFMT " AND fnt=pathname),0) /*%s()*/",
    tid, tid, __func__
  );
  if(rc) goto dberr;

  /*
  ** Add islink information
  */
  rc = fsl_db_exec_multi(dbC,
    "UPDATE fv SET"
    " islinkv=coalesce((SELECT islink FROM vfile"
                       " WHERE vid=%" FSL_ID_T_PFMT
                         " AND fnt=pathname),0),"
    " islinkt=coalesce((SELECT islink FROM vfile"
                       " WHERE vid=%" FSL_ID_T_PFMT
                         " AND fnt=pathname),0) /*%s()*/",
    ckRid, tid, __func__
  );
  if(rc) goto dberr;

  /**
     Right here, fossil(1) permits passing on a subset of
     filenames/dirs to update, but it's apparently a little-used
     feature and we're going to skip it for the time being:

     https://fossil-scm.org/forum/forumpost/1da828facf
   */

  /*
  ** Alter the content of the checkout so that it conforms with the
  ** target
  */
  rc = fsl_db_prepare(dbC, &q,
                      "SELECT fn, idv, ridv, "/* 0..2  */
                      "idt, ridt, chnged, "   /* 3..5  */
                      "fnt, isexe, islinkv, " /* 6..8  */
                      "islinkt, deleted "     /* 9..10 */
                      "FROM fv ORDER BY 1 /*%s()*/",
                      __func__);
  if(rc) goto dberr;
  rc = fsl_db_prepare(dbC, &mtimeXfer,
                      "UPDATE vfile SET mtime=(SELECT mtime FROM vfile "
                      "WHERE id=?1/*idv*/) "
                      "WHERE id=?2/*idt*/ /*%s()*/",
                      __func__);
  if(rc) goto dberr;
  rc = fsl_db_prepare(dbR, &rec.stChanged,
                      "SELECT chnged FROM vfile "
                      "WHERE vid=%" FSL_ID_T_PFMT
                      " AND pathname=? %s /*%s()*/",
                      ckRid, collation, __func__);
  if(rc) goto dberr;
  if(cuOpt->callback){
    /* Queries we need only if we need to collect info for a
       callback... */
    rc = fsl_db_prepare(dbC, &mtimeGet,
                        "SELECT mtime FROM vfile WHERE id=?1"/*idt*/);
    if(rc) goto dberr;
    rc = fsl_db_prepare(dbC, &mtimeSet,
                        "UPDATE vfile SET mtime=?2 WHERE id=?1"/*idt*/);
    if(rc) goto dberr;
    /* Files for which we don't load content still have a size we need
       to report via fsl_ckup_state, and we fetch that with this
       query. */
    rc = fsl_db_prepare(dbR, &rec.stRidSize,
                        "SELECT size FROM blob WHERE rid=?");
    if(rc) goto dberr;
  }

  xState.f = f;
  xState.fCard = &fCard;
  xState.checkinRid = eOpt.checkinRid = tid;
  xState.count.fileCount =
    (uint32_t)fsl_db_g_int32(dbC, 0, "SELECT COUNT(*) FROM vfile "
                             "WHERE vid=%" FSL_ID_T_PFMT,
                             tid);
  uState.extractState = &xState;
  uState.callbackState = cuOpt->callbackState;
  uState.dryRun = cuOpt->dryRun;
  uState.fileRmInfo = FSL_CKUP_RM_NOT;
  rec.originRid = ckRid;
  rec.eOpt = &eOpt;
  rec.cOpt = cuOpt;
  rec.tgtDir = fsl_cx_scratchpad(f);
  rec.tgtDirLen = f->ckout.dirLen;
  rc = fsl_buffer_append(rec.tgtDir, f->ckout.dir,
                         (fsl_int_t)f->ckout.dirLen);
  if(rc) goto end;

  /**
     Missing features from fossil we still need for this include,
     but are not limited to:

     - file_unsafe_in_tree_path() (done, untested)
     - file_nondir_objects_on_path() (done, untested)
     - symlink_create() (done, untested)
     - vfile_to_disk() (done, untested)
     - ...
  */
  bFullPath = fsl_cx_scratchpad(f);
  bFullNewPath = fsl_cx_scratchpad(f);
  bFileUuid = fsl_cx_scratchpad(f);
  rc = fsl_buffer_append(bFullPath, f->ckout.dir,
                         (fsl_int_t)f->ckout.dirLen);
  if(rc) goto end;
  rc = fsl_buffer_append(bFullNewPath, f->ckout.dir,
                         (fsl_int_t)f->ckout.dirLen);
  if(rc) goto end;
  unsigned int nConflict = 0;
  while( FSL_RC_STEP_ROW==fsl_stmt_step(&q) ){
    const char *zName = fsl_stmt_g_text(&q, 0, NULL)
      /* The filename from root */;
    fsl_id_t const idv = fsl_stmt_g_id(&q, 1)
      /* VFILE entry for current */;
    fsl_id_t const ridv = fsl_stmt_g_id(&q, 2)
      /* RecordID for current */;
    fsl_id_t const idt = fsl_stmt_g_id(&q, 3)
      /* VFILE entry for target */;
    fsl_id_t const ridt = fsl_stmt_g_id(&q, 4)
      /* RecordID for target */;
    int const chnged = fsl_stmt_g_int32(&q, 5)
      /* Current is edited */;
    const char *zNewName = fsl_stmt_g_text(&q,6, NULL)
      /* New filename */;
    int const isexe = fsl_stmt_g_int32(&q, 7)
      /* EXE perm for new file */;
    int const islinkv = fsl_stmt_g_int32(&q, 8)
      /* Is current file is a link */;
    int const islinkt = fsl_stmt_g_int32(&q, 9)
      /* Is target file is a link */;
    int const deleted = fsl_stmt_g_int32(&q, 10)
      /* Marked for deletion */;
    char const *zFullPath /* Full pathname of the file */;
    char const *zFullNewPath /* Full pathname of dest */;
    bool const nameChng = !!fsl_strcmp(zName, zNewName)
      /* True if the name changed */;
    int wasWritten = 0
      /* 1=perms written to disk, 2=content written */;
    fsl_fstat fst = fsl_fstat_empty;
    if(chnged || isexe || islinkv || islinkt){/*unused*/}
    
    bFullPath->used = bFullNewPath->used = f->ckout.dirLen;
    rc = fsl_buffer_appendf(bFullPath, zName, -1);
    if(!rc) rc = fsl_buffer_appendf(bFullNewPath, zNewName, -1);
    if(rc) goto end;
    zFullPath = fsl_buffer_cstr(bFullPath);
    zFullNewPath = fsl_buffer_cstr(bFullNewPath);
    uState.mtime = 0;
    uState.fileChangeType = FSL_CKUP_FCHANGE_INVALID;
    uState.fileRmInfo = FSL_CKUP_RM_NOT;
    ++xState.count.fileNumber;
    //MARKER(("#%03u/%03d %s\n", xState.count.fileNumber, xState.count.fileCount, zName));
    if( deleted ){
      /* Carry over pending file deletions from the current version
         into the target version. If the file was already deleted in
         the target version, that will be picked up by the file-deletion
         loop later on. */
      uState.fileChangeType = FSL_CKUP_FCHANGE_RM_PROPAGATED;
      rc = fsl_db_exec(dbC, "UPDATE vfile SET deleted=1 "
                       "WHERE id=%" FSL_ID_T_PFMT" /*%s()*/",
                       idt, __func__);
      if(rc) goto dberr;
    }
    if( idv>0 && ridv==0 && idt>0 && ridt>0 ){
      /* Conflict.  This file has been added to the current checkout
      ** but also exists in the target checkout.  Use the current version.
      */
      uState.fileChangeType = FSL_CKUP_FCHANGE_CONFLICT_ADDED;
      //fossil_print("CONFLICT %s\n", zName);
      nConflict++;
    }else if( idt>0 && idv==0 ){
      /* File added in the target. */
      if( fsl_is_file_or_link(zFullPath) ){
        //fossil_print("ADD %s - overwrites an unmanaged file\n", zName);
        uState.fileChangeType =
          FSL_CKUP_FCHANGE_CONFLICT_ADDED_UNMANAGED;
        //nOverwrite++;
        /* TODO/FIXME: if the files have the same content, treat this
           as FSL_CKUP_FCHANGE_ADDED. If they don't, use confirmer to
           ask the user what to do. */
      }else{
        //fsl_outputf(f, "ADD %s\n", zName);
        uState.fileChangeType = FSL_CKUP_FCHANGE_ADDED;
      }
      //if( !dryRunFlag && !internalUpdate ) undo_save(zName);
      //MARKER(("Here's where we would vfile_to_disk() %s\n", zName));
      if( !cuOpt->dryRun ){
        rc = fsl_vfile_to_ckout(f, idt, &wasWritten);
        if(rc) goto end;
      }
    }else if( idt>0 && idv>0 && ridt!=ridv && (chnged==0 || deleted) ){
      /* The file is unedited.  Change it to the target version */
      if( deleted ){
        //fossil_print("UPDATE %s - change to unmanaged file\n", zName);
        uState.fileChangeType = FSL_CKUP_FCHANGE_RM;
      }else{
        //fossil_print("UPDATE %s\n", zName);
        uState.fileChangeType = FSL_CKUP_FCHANGE_UPDATED;
      }
      if( !cuOpt->dryRun ){
        rc = fsl_vfile_to_ckout(f, idt, &wasWritten);
        if(rc) goto end;
      }
    }else if( idt>0 && idv>0 && !deleted &&
              0!=fsl_stat(zFullPath, NULL, false) ){
      /* The file is missing from the local check-out. Restore it to
      ** the version that appears in the target. */
      uState.fileChangeType = FSL_CKUP_FCHANGE_UPDATED;
      if( !cuOpt->dryRun ){
        rc = fsl_vfile_to_ckout(f, idt, &wasWritten);
        if(rc) goto end;
      }
    }else if( idt==0 && idv>0 ){
      /* Is in the current version but not in the target. */
      if( ridv==0 ){
        /* Added in current checkout.  Continue to hold the file as
        ** as an addition */
        uState.fileChangeType = FSL_CKUP_FCHANGE_ADD_PROPAGATED;
        rc = fsl_db_exec(dbC, "UPDATE vfile SET vid=%" FSL_ID_T_PFMT
                         " WHERE id=%" FSL_ID_T_PFMT " /*%s()*/",
                         tid, idv, __func__);
        if(rc) goto dberr;
      }else if( chnged ){
        /* Edited locally but deleted from the target.  Do not track the
        ** file but keep the edited version around. */
        uState.fileChangeType = FSL_CKUP_FCHANGE_CONFLICT_RM;
        ++nConflict;
        uState.fileRmInfo = FSL_CKUP_RM_KEPT;
        /* Delete idv from vfile so that the post-processing rm
           loop will not delete this file. */
        rc = fsl_db_exec(dbC, "DELETE FROM vfile WHERE id=%"
                         FSL_ID_T_PFMT " /*%s()*/",
                         idv, __func__);
        if(rc) goto dberr;

      }else{
        uState.fileChangeType = FSL_CKUP_FCHANGE_RM;
        if( !cuOpt->dryRun ){
          fsl_file_unlink(zFullPath)/*ignore errors*/;
          /* At this point fossil(1) adds each directory to the
             dir_to_delete table. We can probably use the same
             infrastructure which ckout uses, though. One
             hiccup there is that our infrastructure does not
             handle the locally-modified-removed case from the
             block above this one. */
        }
      }
    }else if( idt>0 && idv>0 && ridt!=ridv && chnged ){
      /* Merge the changes in the current tree into the target version */
      if( islinkv || islinkt ){
        uState.fileChangeType = FSL_CKUP_FCHANGE_CONFLICT_SYMLINK;
        ++nConflict;
      }else{
        unsigned int conflictCount = 0;
        for(int i = 0; i < MergeBufCount; ++i){
          fsl_buffer_reuse(&bufMerge[i]);
        }
        rc = fsl_content_get(f, ridv, &bufMerge[0]);
        if(!rc) rc = fsl_content_get(f, ridt, &bufMerge[2]);
        if(!rc){
          rc = fsl_buffer_fill_from_filename(&bufMerge[1], zFullPath);
        }
        if(rc) goto end;
        rc = fsl_buffer_merge3(&bufMerge[0], &bufMerge[1],
                               &bufMerge[2], &bufMerge[3],
                               &conflictCount);
        if(FSL_RC_TYPE==rc){
          /* Binary content: we can't merge this, so use target
             version. */
          rc = 0;
          uState.fileChangeType = FSL_CKUP_FCHANGE_UPDATED_BINARY;
          if( !cuOpt->dryRun ){
            rc = fsl_buffer_to_filename(&bufMerge[2], zFullNewPath);
            if(!rc) fsl_file_exec_set(zFullNewPath, !!isexe);
          }
        }else if(!rc){
          if( !cuOpt->dryRun ){
            rc = fsl_buffer_to_filename(&bufMerge[3], zFullNewPath);
            if(!rc) fsl_file_exec_set(zFullNewPath, !!isexe);
          }
          uState.fileChangeType = conflictCount
            ? FSL_CKUP_FCHANGE_CONFLICT_MERGED
            : FSL_CKUP_FCHANGE_MERGED;
          if(conflictCount) ++nConflict;
        }
        if(rc) goto end;
      }
      if( nameChng && !cuOpt->dryRun ){
        fsl_file_unlink(zFullPath);
      }
    }else{
      if( chnged ){
        if( !deleted ){
          uState.fileChangeType = FSL_CKUP_FCHANGE_EDITED;
        }else{
          assert(FSL_CKUP_FCHANGE_RM_PROPAGATED==uState.fileChangeType);
        }
      }else{
        uState.fileChangeType = FSL_CKUP_FCHANGE_NONE;
        rc = fsl_stmt_bind_step(&mtimeXfer, "RR", idv, idt);
        if(rc) goto dberr;
      }
    }
    if(wasWritten && cuOpt->setMtime){
      if(0==fsl_mtime_of_manifest_file(f, tid, ridt, &uState.mtime)){
        fsl_file_mtime_set(zFullNewPath, uState.mtime);
        rc = fsl_stmt_bind_step(&mtimeSet, "RI", idt, uState.mtime);
        if(rc) goto dberr;
      }
    }
    assert(FSL_CKUP_FCHANGE_INVALID != uState.fileChangeType);
    assert(!rc);
    if(cuOpt->callback
       && (FSL_CKUP_FCHANGE_RM != uState.fileChangeType)
       /* removals are reported separately in the file
          deletion phase */){
      if(FSL_CKUP_FCHANGE_ADD_PROPAGATED==uState.fileChangeType){
        /* This file is not yet in SCM, so its size is not in
           the db. */
        if(0==fsl_stat(zFullNewPath, &fst, false)){
          uState.size = (fsl_int_t)fst.size;
          uState.mtime = fst.mtime;
        }else{
          uState.size = -1;
        }
      }else{
        /* If we have the record's size in the db, use that. */
        fsl_stmt_bind_id(&rec.stRidSize, 1, ridt);
        if(FSL_RC_STEP_ROW==fsl_stmt_step(&rec.stRidSize)){
          uState.size = fsl_stmt_g_int32(&rec.stRidSize, 0);
        }else{
          uState.size = -1;
        }
        fsl_stmt_reset(&rec.stRidSize);
      }
      if(!uState.mtime){
        fsl_stmt_bind_id(&mtimeGet, 1, idt);
        if(FSL_RC_STEP_ROW==fsl_stmt_step(&mtimeGet)){
          uState.mtime = fsl_stmt_g_id(&mtimeGet, 0);
        }
        if(0==uState.mtime && 0==fsl_stat(zFullNewPath, &fst, false)){
          uState.mtime = fst.mtime;
        }
        fsl_stmt_reset(&mtimeGet);
      }
      xState.fileRid = ridt;
      fCard.name = (char *)zNewName;
      fCard.priorName = (char *)(nameChng ? zName : NULL);
      fCard.perm = islinkt ? FSL_FILE_PERM_LINK
        : (isexe ? FSL_FILE_PERM_EXE : FSL_FILE_PERM_REGULAR);
      if(ridt){
        rc = fsl_rid_to_uuid2(f, ridt, bFileUuid);
        if(rc) goto end;
        fCard.uuid = fsl_buffer_str(bFileUuid);
      }else{
        //MARKER(("ridt=%d uState.fileChangeType=%d name=%s\n",
        //        ridt, uState.fileChangeType, fCard.name));
        assert(FSL_CKUP_FCHANGE_CONFLICT_RM==uState.fileChangeType
               || FSL_CKUP_FCHANGE_ADD_PROPAGATED==uState.fileChangeType
               || FSL_CKUP_FCHANGE_EDITED==uState.fileChangeType
               );
        fCard.uuid = 0;
      }
      rc = cuOpt->callback( &uState );
      if(rc) goto end;
      uState.mtime = 0;
    }
  }/*fsl_stmt_step(&q)*/
  fsl_stmt_finalize(&q);
  if(nConflict){/*unused*/}
  /*
    At this point, fossil(1) does:

    ensure_empty_dirs_created(1);
    checkout_set_all_exe();
  */
  assert(!rc);
  rc = fsl_repo_ckout_rm_list_fini(f, &rec);
  if(!rc){
    rc = fsl_vfile_unload_except(f, tid);
  }
  if(!rc){
    rc = fsl_ckout_version_write(f, tid, 0);
  }
  
  end:
  /* clang bug? If we declare rc2 here, it says "expression expected".
     Moving the decl to the top resolves it. Wha? */
  if(rec.tgtDir) fsl_cx_scratchpad_yield(f, rec.tgtDir);
  if(bFullPath) fsl_cx_scratchpad_yield(f, bFullPath);
  if(bFullNewPath) fsl_cx_scratchpad_yield(f, bFullNewPath);
  if(bFileUuid) fsl_cx_scratchpad_yield(f, bFileUuid);
  for(int i = 0; i < MergeBufCount; ++i){
    fsl_buffer_clear(&bufMerge[i]);
  }
  fsl_stmt_finalize(&rec.stRidSize);
  fsl_stmt_finalize(&rec.stChanged);
  fsl_stmt_finalize(&mtimeGet);
  fsl_stmt_finalize(&mtimeSet);
  fsl_stmt_finalize(&q);
  fsl_stmt_finalize(&mtimeXfer);
  fsl_db_exec(dbC, "DROP TABLE fv /*%s()*/", __func__);
  rc2 = fsl_db_transaction_end(dbC, !!rc);
  return rc ? rc : rc2;
  dberr:
  assert(rc);
  rc = fsl_cx_uplift_db_error2(f, dbC, rc);
  goto end;
}


/** Helper for generating a list of ambiguous leaf UUIDs. */
struct AmbiguousLeavesOutput {
  int count;
  int rc;
  fsl_buffer * buffer;
};
typedef struct AmbiguousLeavesOutput AmbiguousLeavesOutput;
static const AmbiguousLeavesOutput AmbiguousLeavesOutput_empty =
  {0, 0, NULL};

static int fsl_stmt_each_f_ambiguous_leaves( fsl_stmt * stmt, void * state ){
  AmbiguousLeavesOutput * alo = (AmbiguousLeavesOutput*)state;
  if(alo->count++){
    alo->rc = fsl_buffer_append(alo->buffer, ", ", 2);
  }
  if(!alo->rc){
    fsl_size_t n = 0;
    char const * uuid = fsl_stmt_g_text(stmt, 0, &n);
    assert(n==FSL_STRLEN_SHA1 || n==FSL_STRLEN_K256);
    alo->rc = fsl_buffer_append(alo->buffer, uuid, 16);
  }
  return alo->rc;
}

int fsl_ckout_calc_update_version(fsl_cx * f, fsl_id_t * outRid){
  fsl_db * const dbRepo = fsl_needs_repo(f);
  if(!dbRepo) return FSL_RC_NOT_A_REPO;
  else if(!fsl_needs_ckout(f)) return FSL_RC_NOT_A_CKOUT;
  int rc = 0;
  fsl_id_t tgtRid = 0;
  fsl_leaves_compute_e leafMode = FSL_LEAVES_COMPUTE_OPEN;
  fsl_id_t const ckRid = f->ckout.rid;
  rc = fsl_leaves_compute(f, ckRid, leafMode);
  if(rc) goto end;
  if( !fsl_leaves_computed_has(f) ){
    leafMode = FSL_LEAVES_COMPUTE_ALL;
    rc = fsl_leaves_compute(f, ckRid, leafMode);
    if(rc) goto end;
  }
  /* Delete [leaves] entries from any branches other than
     ckRid's... */
  rc = fsl_db_exec_multi(dbRepo,
        "DELETE FROM leaves WHERE rid NOT IN"
        "   (SELECT leaves.rid FROM leaves, tagxref"
        "     WHERE leaves.rid=tagxref.rid AND tagxref.tagid=%d"
        "       AND tagxref.value==(SELECT value FROM tagxref"
                                   " WHERE tagid=%d AND rid=%"
                             FSL_ID_T_PFMT "))",
        FSL_TAGID_BRANCH, FSL_TAGID_BRANCH, ckRid
  );
  if(rc) goto end;
  else if( fsl_leaves_computed_count(f)>1 ){
    AmbiguousLeavesOutput alo = AmbiguousLeavesOutput_empty;
    alo.buffer = fsl_cx_scratchpad(f);
    rc = fsl_buffer_append(alo.buffer,
                           "Multiple viable descendants found: ", -1);
    if(!rc){
      fsl_stmt q = fsl_stmt_empty;
      rc = fsl_db_prepare(dbRepo, &q, "SELECT uuid FROM blob "
                          "WHERE rid IN leaves ORDER BY uuid");
      if(!rc){
        rc = fsl_stmt_each(&q, fsl_stmt_each_f_ambiguous_leaves, &alo);
      }
      fsl_stmt_finalize(&q);
    }
    if(!rc){
      rc = fsl_cx_err_set(f, FSL_RC_AMBIGUOUS, "%b", alo.buffer);
    }
    fsl_cx_scratchpad_yield(f, alo.buffer);
  }
  end:
  if(!rc){
    tgtRid = fsl_leaves_computed_latest(f);
    *outRid = tgtRid;
    fsl_leaves_computed_cleanup(f)
      /* We might want to keep [leaves] around for the case where we
         return FSL_RC_AMBIGUOUS, to give the client a way to access
         that list in its raw form. Higher-level code could join that
         with the event table to give the user more context. */;
  }
  return rc;
}

void fsl_ckout_manifest_setting(fsl_cx *f, int *m){
  if(!m){
    f->cache.manifestSetting = -1;
    return;
  }else if(f->cache.manifestSetting>=0){
    *m = f->cache.manifestSetting;
    return;
  }
  char * str = fsl_config_get_text(f, FSL_CONFDB_VERSIONABLE,
                                   "manifest", NULL);
  if(!str){
    str = fsl_config_get_text(f, FSL_CONFDB_REPO,
                              "manifest", NULL);
  }
  *m = 0;
  if(str){
    char const * z = str;
    if('1'==*z || 0==fsl_strncmp(z,"on",2)
       || 0==fsl_strncmp(z,"true",4)){
      z = "ru"/*historical default*/;
    }else if(!fsl_str_bool(z)){
      z = "";
    }
    for(;*z;++z){
      switch(*z){
        case 'r': *m |= 0x001; break;
        case 'u': *m |= 0x010; break;
        case 't': *m |= 0x100; break;
        default: break;
      }
    }
    fsl_free(str);
  }
  f->cache.manifestSetting = (short)*m;
}

int fsl_ckout_manifest_write(fsl_cx *f, int manifest, int manifestUuid,
                             int manifestTags,
                             int * wrote){
  fsl_db * const db = fsl_needs_ckout(f);
  if(!db) return FSL_RC_NOT_A_CKOUT;
  else if(!f->ckout.rid){
    return fsl_cx_err_set(f, FSL_RC_RANGE,
                          "Checkout RID is 0, so it has no manifest.");
  }
  int W = 0;
  int rc = 0;
  fsl_buffer * b = fsl_cx_scratchpad(f);
  fsl_buffer * content = &f->fileContent;
  char * str = 0;
  fsl_time_t const mtime = f->ckout.mtime>0
    ? fsl_julian_to_unix(f->ckout.mtime)
    : 0;
  fsl_buffer_reuse(content);
  if(manifest<0 || manifestUuid<0 || manifestTags<0){
    int setting = 0;
    fsl_ckout_manifest_setting(f, &setting);
    if(manifest<0 && setting & FSL_MANIFEST_MAIN) manifest=1;
    if(manifestUuid<0 && setting & FSL_MANIFEST_UUID) manifestUuid=1;
    if(manifestTags<0 && setting & FSL_MANIFEST_TAGS) manifestTags=1;
  }
  if(manifest || manifestUuid || manifestTags){
    rc = fsl_buffer_append(b, f->ckout.dir, (fsl_int_t)f->ckout.dirLen);
    if(rc) goto end;
  }
  if(manifest>0){
    rc = fsl_buffer_append(b, "manifest", 8);
    if(rc) goto end;
    rc = fsl_content_get(f, f->ckout.rid, content);
    if(rc) goto end;
    rc = fsl_buffer_to_filename(content, fsl_buffer_cstr(b));
    if(rc){
      rc = fsl_cx_err_set(f, rc, "Error writing file: %b", b);
      goto end;
    }
    if(mtime) fsl_file_mtime_set(fsl_buffer_cstr(b), mtime);
    W |= FSL_MANIFEST_MAIN;
  }else if(!fsl_db_exists(db,
                          "SELECT 1 FROM vfile WHERE "
                          "pathname='manifest' /*%s()*/",
                          __func__)){
    b->used = f->ckout.dirLen;
    rc = fsl_buffer_append(b, "manifest", 8);
    if(rc) goto end;
    fsl_file_unlink(fsl_buffer_cstr(b));
  }

  if(manifestUuid>0){
    b->used = f->ckout.dirLen;
    fsl_buffer_reuse(content);
    rc = fsl_buffer_append(b, "manifest.uuid", 13);
    if(rc) goto end;
    assert(f->ckout.uuid);
    rc = fsl_buffer_append(content, f->ckout.uuid, -1);
    if(!rc) rc = fsl_buffer_append(content, "\n", 1);
    if(rc) goto end;
    rc = fsl_buffer_to_filename(content, fsl_buffer_cstr(b));
    if(rc){
      rc = fsl_cx_err_set(f, rc, "Error writing file: %b", b);
      goto end;
    }
    if(mtime) fsl_file_mtime_set(fsl_buffer_cstr(b), mtime);
    W |= FSL_MANIFEST_UUID;
  }else if(!fsl_db_exists(db,
                          "SELECT 1 FROM vfile WHERE "
                          "pathname='manifest.uuid' /*%s()*/",
                          __func__)){
    b->used = f->ckout.dirLen;
    rc = fsl_buffer_append(b, "manifest.uuid", 13);
    if(rc) goto end;
    fsl_file_unlink(fsl_buffer_cstr(b));
  }

  if(manifestTags>0){
    fsl_stmt q = fsl_stmt_empty;
    fsl_db * const db = fsl_cx_db_repo(f);
    assert(db && "We can't have a checkout w/o a repo.");
    b->used = f->ckout.dirLen;
    fsl_buffer_reuse(content);
    rc = fsl_buffer_append(b, "manifest.tags", 13);
    if(rc) goto end;
    str = fsl_db_g_text(db, NULL, "SELECT VALUE FROM tagxref "
                        "WHERE rid=%" FSL_ID_T_PFMT
                        " AND tagid=%d /*%s()*/",
                        f->ckout.rid, FSL_TAGID_BRANCH, __func__);
    rc = fsl_buffer_appendf(content, "branch %z\n", str);
    str = 0;
    if(rc) goto end;
    rc = fsl_db_prepare(db, &q,
                        "SELECT substr(tagname, 5)"
                        "  FROM tagxref, tag"
                        " WHERE tagxref.rid=%" FSL_ID_T_PFMT
                        "   AND tagxref.tagtype>0"
                        "   AND tag.tagid=tagxref.tagid"
                        "   AND tag.tagname GLOB 'sym-*'"
                        " /*%s()*/",
                        f->ckout.rid, __func__);
    if(rc) goto end;
    while( FSL_RC_STEP_ROW==fsl_stmt_step(&q) ){
      const char *zName = fsl_stmt_g_text(&q, 0, NULL);
      rc = fsl_buffer_appendf(content, "tag %s\n", zName);
      if(rc) break;
    }
    fsl_stmt_finalize(&q);
    if(!rc){
      rc = fsl_buffer_to_filename(content, fsl_buffer_cstr(b));
      if(rc){
        rc = fsl_cx_err_set(f, rc, "Error writing file: %b", b);
      }
    }
    if(mtime) fsl_file_mtime_set(fsl_buffer_cstr(b), mtime);
    W |= FSL_MANIFEST_TAGS;
  }else if(!fsl_db_exists(db,
                          "SELECT 1 FROM vfile WHERE "
                          "pathname='manifest.tags' /*%s()*/",
                          __func__)){
    b->used = f->ckout.dirLen;
    rc = fsl_buffer_append(b, "manifest.tags", 13);
    if(rc) goto end;
    fsl_file_unlink(fsl_buffer_cstr(b));
  }

  end:
  if(wrote) *wrote = W;
  fsl_cx_scratchpad_yield(f, b);
  fsl_buffer_reuse(content);
  return rc;
}

/**
   Check every sub-directory of f's current checkout dir along the
   path to zFilename. If any sub-directory part is really an ordinary file
   or a symbolic link, set *errLen to the length of the prefix of zFilename
   which is the name of that object.

   Returns 0 except on allocation error, in which case it returned FSL_RC_OOM.
   If it finds nothing untowards about the path, *errLen will be set to 0.
   
   Example:  Given inputs
   
   ckout     = /home/alice/project1
   zFilename = /home/alice/project1/main/src/js/fileA.js
   
   Look for objects in the following order:
   
   /home/alice/project/main
   /home/alice/project/main/src
   /home/alice/project/main/src/js
   
   If any of those objects exist and are something other than a
   directory then *errLen will be the length of the name of the first
   non-directory object seen.

   If a given element of the path does not exist in the filesystem,
   traversal stops without an error.
*/
static int fsl_ckout_nondir_file_check(fsl_cx *f, char const * zFilename,
                                       fsl_size_t * errLen);

int fsl_ckout_nondir_file_check(fsl_cx *f, char const * zFilename,
                                fsl_size_t * errLen){
  if(!fsl_needs_ckout(f)) return FSL_RC_NOT_A_CKOUT;
  int rc = 0;
  int frc;
  fsl_buffer * const fn = fsl_cx_scratchpad(f);
  if(!fsl_is_rooted_in_ckout(f, zFilename)){
    assert(!"Misuse of this API. This condition should never fail.");
    rc = fsl_cx_err_set(f, FSL_RC_MISUSE, "Path is not rooted at the "
                        "current checkout directory: %s", zFilename);
    goto end;
  }
  rc = fsl_buffer_append(fn, zFilename, -1);
  if(rc) goto end;
  char * z = fsl_buffer_str(fn);
  fsl_size_t i = f->ckout.dirLen;
  fsl_size_t j;
  fsl_fstat fst = fsl_fstat_empty;
  char const * const zRoot = f->ckout.dir;
  if(i && '/'==zRoot[i-1]) --i;
  *errLen = 0;
  while( z[i]=='/' ){
    for(j=i+1; z[j] && z[j]!='/'; ++j){}
    if( z[j]!='/' ) break;
    z[j] = 0;
    frc = fsl_stat(z, &fst, false);
    if(frc){
      /* A not[-yet]-existing path element is okay */
      break;
    }
    if(FSL_FSTAT_TYPE_DIR!=fst.type){
      *errLen = j;
      break;
    }
    z[j] = '/';
    i = j;
  }
  end:
  fsl_cx_scratchpad_yield(f, fn);
  return rc;
}

int fsl_ckout_safe_file_check(fsl_cx *f, char const * zFilename){
  if(!fsl_needs_ckout(f)) return FSL_RC_NOT_A_CKOUT;
  int rc = 0;
  fsl_buffer * const fn = fsl_cx_scratchpad(f);
  if(!fsl_is_absolute_path(zFilename)){
    rc = fsl_file_canonical_name2(f->ckout.dir, zFilename, fn, false);
    if(rc) goto end;
    zFilename = fsl_buffer_cstr(fn);
  }else if(!fsl_is_rooted_in_ckout(f, zFilename)){
    rc = fsl_cx_err_set(f, FSL_RC_MISUSE, "Path is not rooted at the "
                        "current checkout directory: %s", zFilename);
    goto end;
  }

  fsl_size_t errLen = 0;
  rc = fsl_ckout_nondir_file_check(f, zFilename, &errLen);
  if(rc) goto end /* OOM */;
  else if(errLen){
    rc = fsl_cx_err_set(f, FSL_RC_TYPE, "Directory part of path refers "
                        "to a non-directory: %.*s",
                        (int)errLen, zFilename);
  }
  end:
  fsl_cx_scratchpad_yield(f, fn);
  return rc;
}

bool fsl_is_rooted_in_ckout(fsl_cx *f, char const *zAbsPath){
  return f->ckout.dir
    ? 0==fsl_strncmp(zAbsPath, f->ckout.dir, f->ckout.dirLen)
    /* ^^^ fossil(1) uses stricmp() there, but that's a bug. However,
       NOT using stricmp() on case-insensitive filesystems is arguably
       also a bug. */
    : false;
}

int fsl_is_rooted_in_ckout2(fsl_cx *f, char const *zAbsPath){
  int rc = 0;
  if(!fsl_is_rooted_in_ckout(f, zAbsPath)){
    rc = fsl_cx_err_set(f, FSL_RC_RANGE, "Path is not rooted "
                        "in the current checkout: %s",
                        zAbsPath);
  }
  return rc;
}

int fsl_ckout_symlink_create(fsl_cx * f, char const *zTgtFile,
                             char const * zLinkFile){
  if(!fsl_needs_ckout(f)) return FSL_RC_NOT_A_CKOUT;
  int rc = 0;
  fsl_buffer * const fn = fsl_cx_scratchpad(f);
  if(!fsl_is_absolute_path(zLinkFile)){
    rc = fsl_file_canonical_name2(f->ckout.dir, zLinkFile, fn, false);
    if(rc) goto end;
    zLinkFile = fsl_buffer_cstr(fn);
  }else if(0!=(rc = fsl_is_rooted_in_ckout2(f, zLinkFile))){
    goto end;
  }
  fsl_buffer * const b = fsl_cx_scratchpad(f);
  rc = fsl_buffer_append(b, zTgtFile, -1);
  if(!rc){
    rc = fsl_buffer_to_filename(b, fsl_buffer_cstr(fn));
  }
  fsl_cx_scratchpad_yield(f, b);
  end:
  fsl_cx_scratchpad_yield(f, fn);
  return rc;
}

/**
   Queues the directory part of the given filename into temp table
   fx_revert_rmdir for an eventual rmdir() attempt on it in
   fsl_revert_rmdir_fini().
*/
static int fsl_revert_rmdir_queue(fsl_cx * f, fsl_db * db, fsl_stmt * st,
                                  char const * zFilename){
  int rc = 0;
  if( !st->stmt ){
    rc = fsl_db_exec(db, "CREATE TEMP TABLE IF NOT EXISTS "
                     "fx_revert_rmdir(n TEXT PRIMARY KEY) "
                     "WITHOUT ROWID /* %s() */", __func__);
    if(rc) goto dberr;
    rc = fsl_db_prepare(db, st, "INSERT OR IGNORE INTO "
                        "fx_revert_rmdir(n) "
                        "VALUES(fsl_dirpart(?,0)) /* %s() */",
                        __func__);
    if(rc) goto dberr;
  }
  rc = fsl_stmt_bind_step(st, "s", zFilename);
  if(rc) goto dberr;
  end:
  return rc;
  dberr:
  rc = fsl_cx_uplift_db_error2(f, db, rc);
  goto end;
}

/**
   Attempts to rmdir all dirs queued by fsl_revert_rmdir_queue(). Silently
   ignores rmdir failure but will return non-0 for db errors.
*/
static int fsl_revert_rmdir_fini(fsl_cx * f, fsl_db * db){
  int rc;
  fsl_stmt st = fsl_stmt_empty;
  fsl_buffer * const b = fsl_cx_scratchpad(f);
  rc = fsl_db_prepare(db, &st,
                      "SELECT fsl_ckout_dir()||n "
                      "FROM fx_revert_rmdir "
                      "ORDER BY length(n) DESC /* %s() */",
                      __func__);
  if(rc) goto dberr;
  while(FSL_RC_STEP_ROW == fsl_stmt_step(&st)){
    fsl_size_t nDir = 0;
    char const * zDir = fsl_stmt_g_text(&st, 0, &nDir);
    fsl_buffer_reuse(b);
    rc = fsl_buffer_append(b, zDir, (fsl_int_t)nDir);
    if(rc) break;
    fsl_ckout_rm_empty_dirs(f, b);
  }
  end:
  fsl_cx_scratchpad_yield(f, b);
  fsl_stmt_finalize(&st);
  return rc;
  dberr:
  rc = fsl_cx_uplift_db_error2(f, db, rc);
  goto end;
}

int fsl_ckout_revert( fsl_cx * f, fsl_ckout_revert_opt const * opt ){
  /**
     Reminder to whoever works on this code: the initial
     implementation was done almost entirely without the benefit of
     looking at fossil's implementation, thus this code is notably
     different from fossil's. If any significant misbehaviors are
     found here, vis a vis fossil, it might be worth reverting (as it
     were) to that implementation.
  */
  int rc;
  fsl_db * const db = fsl_needs_ckout(f);
  fsl_buffer * fname = 0;
  char const * zNorm = 0;
  fsl_id_t const vid = f->ckout.rid;
  bool inTrans = false;
  fsl_stmt q = fsl_stmt_empty;
  fsl_stmt vfUpdate = fsl_stmt_empty;
  fsl_stmt qRmdir = fsl_stmt_empty;
  fsl_buffer * sql = 0;
  if(!db) return FSL_RC_NOT_A_CKOUT;
  assert(vid>=0);
  if(!opt->vfileIds && opt->filename && *opt->filename){
    fname = fsl_cx_scratchpad(f);
    rc = fsl_ckout_filename_check(f, opt->relativeToCwd,
                                  opt->filename, fname);
    if(rc){
      fsl_cx_scratchpad_yield(f, fname);
      return rc;
    }
    zNorm = fsl_buffer_cstr(fname);
    /* MARKER(("fsl_ckout_unmanage(%d, %s) ==> %s\n", opt->relativeToCwd, opt->filename, zNorm)); */
    assert(zNorm);
    if(fname->used) fsl_buffer_strip_slashes(fname);
    if(1==fname->used && '.'==*zNorm){
      /* Special case: handle "." from ckout root intuitively */
      fsl_buffer_reuse(fname);
      assert(0==*zNorm);
    }
  }
  rc = fsl_db_transaction_begin(db);
  if(rc) goto dberr;
  inTrans = true;
  if(opt->scanForChanges){
    rc = fsl_vfile_changes_scan(f, 0, 0);
    if(rc) goto end;
  }
  sql = fsl_cx_scratchpad(f);
  rc = fsl_buffer_appendf(sql, 
                          "SELECT id, rid, deleted, "
                          "fsl_ckout_dir()||pathname, "
                          "fsl_ckout_dir()||origname "
                          "FROM vfile WHERE vid=%" FSL_ID_T_PFMT " ",
                          vid);
  if(rc) goto end;
  if(zNorm && *zNorm){
    rc = fsl_buffer_appendf(sql,
                            "AND CASE WHEN %Q='' THEN 1 "
                            "ELSE ("
                            "     fsl_match_vfile_or_dir(pathname,%Q) "
                            "  OR fsl_match_vfile_or_dir(origname,%Q)"
                            ") END",
                            zNorm, zNorm, zNorm);
    if(rc) goto end;
  }else if(opt->vfileIds){
    rc = fsl_ckout_bag_to_ids(f, db, "fx_revert_id", opt->vfileIds);
    if(rc) goto end;
    rc = fsl_buffer_append(sql, "AND id IN fx_revert_id", -1);
    if(rc) goto end;
  }else{
    rc = fsl_buffer_append(sql,
                           "AND ("
                           " chnged<>0"
                           " OR deleted<>0"
                           " OR rid=0"
                           " OR coalesce(origname,pathname)"
                           "    <>pathname"
                           ")", -1);
  }
  assert(!rc);
  rc = fsl_db_prepare(db, &q, "%b /* %s() */", sql, __func__);
  fsl_cx_scratchpad_yield(f, sql);
  sql = 0;
  if(rc) goto dberr;
  if((!zNorm || !*zNorm) && !opt->vfileIds){
    rc = fsl_ckout_clear_merge_state(f);
    if(rc) goto end;
  }
  while((FSL_RC_STEP_ROW==fsl_stmt_step(&q))){
    fsl_id_t const id = fsl_stmt_g_id(&q, 0);
    fsl_id_t const rid = fsl_stmt_g_id(&q, 1);
    int32_t const deleted = fsl_stmt_g_int32(&q, 2);
    char const * const zName = fsl_stmt_g_text(&q, 3, NULL);
    char const * const zNameOrig = fsl_stmt_g_text(&q, 4, NULL);
    bool const renamed =
      zNameOrig ? !!fsl_strcmp(zName, zNameOrig) : false;
    fsl_ckout_revert_e changeType = FSL_REVERT_NONE;
    if(!rid){ // Added but not yet checked in.
      rc = fsl_db_exec(db, "DELETE FROM vfile WHERE id=%" FSL_ID_T_PFMT,
                       id);
      if(rc) goto dberr;
      changeType = FSL_REVERT_UNMANAGE;
    }else{
      int wasWritten = 0;
      if(renamed){
        if((rc=fsl_mkdir_for_file(zNameOrig, true))){
          rc = fsl_cx_err_set(f, rc, "mkdir() failed for file: %s",
                              zNameOrig);
          break;
        }
        /* Move, if possible, the new name back over the original
           name. This will possibly allow fsl_vfile_to_ckout() to
           avoid having to load that file's contents and overwrite
           it. */
        int mvCheck = fsl_stat(zName, NULL, false);
        if(0==mvCheck || FSL_RC_NOT_FOUND==mvCheck){
          mvCheck = fsl_file_unlink(zNameOrig);
          if(0==mvCheck || FSL_RC_NOT_FOUND==mvCheck){
            if(0==fsl_file_rename(zName, zNameOrig)){
              rc = fsl_revert_rmdir_queue(f, db, &qRmdir, zName);
              if(rc) break;
            }
          }
        }
        /* Ignore any errors: this operation is an optimization,
           not a requirement. Worse case, the entry with the old
           name is left in the filesystem. */
      }
      if(!vfUpdate.stmt){
        rc = fsl_db_prepare(db, &vfUpdate,
                            "UPDATE vfile SET chnged=0, deleted=0, "
                            "pathname=coalesce(origname,pathname), "
                            "origname=NULL "
                            "WHERE id=?1 /*%s()*/", __func__);
        if(rc) goto dberr;
      }
      rc = fsl_stmt_bind_step(&vfUpdate, "R", id)
        /* Has to be done before fsl_vfile_to_ckout() because that
           function writes to vfile.pathname. */;
      if(rc) goto dberr;
      rc = fsl_vfile_to_ckout(f, id, &wasWritten);
      if(rc) break;
      if(opt->callback){
        if(renamed){
          changeType = FSL_REVERT_RENAME;
        }else if(wasWritten){
          changeType = (2==wasWritten)
            ? FSL_REVERT_CONTENTS
            : FSL_REVERT_PERMISSIONS;
        }else if(deleted){
          changeType = FSL_REVERT_REMOVE;
        }
      }
    }
    if(opt->callback && FSL_REVERT_NONE!=changeType){
      char const * name = renamed ? zNameOrig : zName;
      rc = opt->callback(&name[f->ckout.dirLen],
                         changeType, opt->callbackState);
      if(rc) break;
    }
  }/*step() loop*/
  end:
  if(fname) fsl_cx_scratchpad_yield(f, fname);
  if(sql) fsl_cx_scratchpad_yield(f, sql);
  fsl_stmt_finalize(&q);
  fsl_stmt_finalize(&vfUpdate);
  if(qRmdir.stmt){
    fsl_stmt_finalize(&qRmdir);
    if(!rc) rc = fsl_revert_rmdir_fini(f, db);
    fsl_db_exec(db, "DROP TABLE IF EXISTS fx_revert_rmdir /* %s() */",
                __func__);
  }
  if(opt->vfileIds){
    fsl_db_exec_multi(db, "DROP TABLE IF EXISTS fx_revert_id "
                      "/* %s() */", __func__)
      /* Ignoring result code */;
  }
  if(inTrans){
    int const rc2 = fsl_db_transaction_end(db, !!rc);
    if(!rc) rc = rc2;
  }
  return rc;
  dberr:
  assert(rc);
  rc = fsl_cx_uplift_db_error2(f, db, rc);
  goto end;
}

int fsl_ckout_vfile_ids( fsl_cx * f, fsl_id_t vid,
                         fsl_id_bag * dest, char const * zName,
                         bool relativeToCwd, bool changedOnly ) {
  if(!fsl_needs_ckout(f)) return FSL_RC_NOT_A_CKOUT;
  fsl_buffer * const canon = fsl_cx_scratchpad(f);
  int rc = fsl_ckout_filename_check(f, relativeToCwd, zName, canon);
  if(!rc){
    fsl_buffer_strip_slashes(canon);
    rc = fsl_filename_to_vfile_ids(f, vid, dest,
                                   fsl_buffer_cstr(canon),
                                   changedOnly);
  }
  fsl_cx_scratchpad_yield(f, canon);
  return rc;
}

#undef MARKER
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




















































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/cli.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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).
*/

#include "fossil-scm/fossil-internal.h"
#include "fossil-scm/fossil-cli.h"

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

/** Convenience form of FCLI_VN for level-3 verbosity. */
#define FCLI_V3(pfexp) FCLI_VN(3,pfexp)
#define fcli_empty_m {    \
  NULL/*appHelp*/,  \
  NULL/*cliFlags*/,      \
  NULL/*f*/,      \
  NULL/*argv*/,      \
  0/*argc*/,      \
  NULL/*appName*/,      \
  {/*clientFlags*/ \
    "."/*checkoutDir*/,      \
    0/*verbose*/,            \
    false/*dryRun*/ \
  }, \
  {/*transient*/      \
    NULL/*repoDb*/,      \
    NULL/*userArg*/, \
    0/*helpRequested*/                   \
  },                                                            \
  {/*config*/      \
    -1/*traceSql*/,      \
    fsl_outputer_empty_m      \
  },                          \
  fsl_error_empty_m/*err*/  \
}

const fcli_t fcli_empty = fcli_empty_m;
fcli_t fcli = fcli_empty_m;
const fcli_cliflag fcli_cliflag_empty = fcli_cliflag_empty_m;
static fsl_timer_state fcliTimer = fsl_timer_state_empty_m;

void fcli_printf(char const * fmt, ...){
  va_list args;
  va_start(args,fmt);
  if(fcli.f){
    fsl_outputfv(fcli.f, fmt, args);
  }else{
    fsl_fprintfv(stdout, fmt, args);
  }
  va_end(args);
}

/**
   Outputs app-level help. How it does this depends on the state of
   the fcli object, namely fcli.cliFlags and the verbosity
   level. Normally this is triggered automatically by the CLI flag
   handling in fcli_setup().
*/
static void fcli_help(void);

unsigned short fcli_is_verbose(void){
  return fcli.clientFlags.verbose;
}

fsl_cx * fcli_cx(void){
  return fcli.f;
}

static int fcli_open(void){
  int rc = 0;
  fsl_cx * f = fcli.f;
  assert(f);
  if(fcli.transient.repoDbArg){
    FCLI_V3(("Trying to open repo db file [%s]...\n", fcli.transient.repoDbArg));
    rc = fsl_repo_open( f, fcli.transient.repoDbArg );
  }
  else if(fcli.clientFlags.checkoutDir){
    fsl_buffer dir = fsl_buffer_empty;
    char const * dirName;
    rc = fsl_file_canonical_name(fcli.clientFlags.checkoutDir,
                                 &dir, 0);
    assert(!rc);
    dirName = (char const *)fsl_buffer_cstr(&dir);
    FCLI_V3(("Trying to open checkout from [%s]...\n",
             dirName));
    rc = fsl_ckout_open_dir(f, dirName, true);
    FCLI_V3(("checkout open rc=%s\n", fsl_rc_cstr(rc)));

    /* if(FSL_RC_NOT_FOUND==rc) rc = FSL_RC_NOT_A_CKOUT; */
    if(rc){
      if(!fsl_cx_err_get(f,NULL,NULL)){
        rc = fsl_cx_err_set(f, rc, "Opening of checkout under "
                            "[%s] failed with code %d (%s).",
                            dirName, rc, fsl_rc_cstr(rc));
      }
    }
    fsl_buffer_reserve(&dir, 0);
    if(rc) return rc;
  }
  if(!rc){
    if(fcli.clientFlags.verbose>1){
      fsl_db * dbC = fsl_cx_db_ckout(f);
      fsl_db * dbR = fsl_cx_db_repo(f);
      if(dbC){
        FCLI_V3(("Checkout DB name: %s\n", f->ckout.db.filename));
      }
      if(dbR){
        FCLI_V3(("Opened repo db: %s\n", f->repo.db.filename));
        FCLI_V3(("Repo user name: %s\n", f->repo.user));
      }
    }
#if 0
    /*
      Only(?) here for testing purposes.

       We don't really need/want to update the repo db on each
       open of the checkout db, do we? Or do we?
     */
    fsl_repo_record_filename(f) /* ignore rc - not critical */;
#endif
  }
  return rc;
}


#define fcli__error (fcli.f ? &fcli.f->error : &fcli.err)
fsl_error * fcli_error(void){
  return fcli__error;
}

void fcli_err_reset(void){
  fsl_error_reset(fcli__error);
}


static struct TempFlags {
  bool traceSql;
  bool doTimer;
} TempFlags = {
false,
false
};

static struct {
  fsl_list list;
}  FCliFree = {
fsl_list_empty_m
};

static void fcli_shutdown(void){
  fsl_cx * f = fcli.f;
  int rc = 0;
 
  fsl_error_clear(&fcli.err);
  fsl_free(fcli.argv)/*contents are in the FCliFree list*/;

  if(f){
    while(fsl_cx_transaction_level(f)){
      MARKER(("WARNING: open db transaction at shutdown-time. "
              "Rolling back.\n"));
      fsl_cx_transaction_end(f, true);
    }
    if(1 &&
       fsl_cx_db_ckout(f)){
      /* For testing/demo only: this is implicit
         when we call fsl_cx_finalize().
      */
      rc = fsl_ckout_close(f);
      FCLI_V3(("Closed checkout/repo db(s). rc=%s\n", fsl_rc_cstr(rc)));
      //assert(0==rc);
    }
  }
  fsl_list_clear(&FCliFree.list, fsl_list_v_fsl_free, 0);
  fsl_list_reserve(&FCliFree.list, 0);
  if(f){
    FCLI_V3(("Finalizing fsl_cx @%p\n", (void const *)f));
    fsl_cx_finalize( f );
  }
  fcli = fcli_empty;
  if(TempFlags.doTimer){
    double const runTime =
      ((int64_t)fsl_timer_stop(&fcliTimer)) / 1000.0;
    f_out("Total fcli run time: %f seconds of CPU time\n",
          runTime/1000);
  }
}

static struct {
  fcli_cliflag const * flags;
} FCliHelpState = {
NULL
};


static int fcli_flag_f_nocheckoutDir(fcli_cliflag const *f){
  if(f){/*unused*/}
  fcli.clientFlags.checkoutDir = 0;
  return 0;
}
static int fcli_flag_f_verbose(fcli_cliflag const *f){
  if(f){/*unused*/}
  ++fcli.clientFlags.verbose;
  return FCLI_RC_FLAG_AGAIN;
}
static int fcli_flag_f_help(fcli_cliflag const *f){
  if(f){/*unused*/}
  ++fcli.transient.helpRequested;
  return FCLI_RC_FLAG_AGAIN;
}

static const fcli_cliflag FCliFlagsGlobal[] = {
  FCLI_FLAG_BOOL_X("?","help",NULL,
                   fcli_flag_f_help,
                   "Show app help. Also triggered if the first non-flag is \"help\"."),
  FCLI_FLAG("R","repo","REPO-FILE",&fcli.transient.repoDbArg,
            "Selects a specific repository database, ignoring the one "
            "used by the current directory's checkout (if any)."),
  FCLI_FLAG_BOOL("D","dry-run",&fcli.clientFlags.dryRun,
                 "Enable dry-run mode (not supported by all apps)."),
  FCLI_FLAG("U","user","username",&fcli.transient.userArg,
            "Sets the name of the fossil user name for this session."),
  FCLI_FLAG_BOOL_X("C", "no-checkout",NULL,fcli_flag_f_nocheckoutDir,
                   "Disable automatic attempt to open checkout."),
  FCLI_FLAG(0,"checkout-dir","DIRECTORY", &fcli.clientFlags.checkoutDir,
            "Open the given directory as a checkout, instead of the current dir."),
  FCLI_FLAG_BOOL_X("V","verbose",NULL,fcli_flag_f_verbose,
              "Increases the verbosity level by 1. May be used multiple times."),
  FCLI_FLAG_BOOL(0,"trace-sql",&TempFlags.traceSql,
                 "Enable SQL tracing."),
  FCLI_FLAG_BOOL(0,"timer",&TempFlags.doTimer,
                 "At the end of successful app execution, output how long it took "
                 "from the call to fcli_setup() until the end of main()."),
  fcli_cliflag_empty_m
};

void fcli_cliflag_help(fcli_cliflag const *defs){
  fcli_cliflag const * f;
  const char * tab = "  ";
  for( f = defs; f->flagShort || f->flagLong; ++f ){
    const char * s = f->flagShort;
    const char * l = f->flagLong;
    const char * fvl = f->flagValueLabel;
    const char * valLbl = 0;
    switch(f->flagType){
      case FCLI_FLAG_TYPE_BOOL:
      case FCLI_FLAG_TYPE_BOOL_INVERT: break;
      case FCLI_FLAG_TYPE_INT32: valLbl = fvl ? fvl : "int32"; break;
      case FCLI_FLAG_TYPE_INT64: valLbl = fvl ? fvl : "int64"; break;
      case FCLI_FLAG_TYPE_ID: valLbl = fvl ? fvl : "db-record-id"; break;
      case FCLI_FLAG_TYPE_DOUBLE: valLbl = fvl ? fvl : "double"; break;
      case FCLI_FLAG_TYPE_CSTR: valLbl = fvl ? fvl : "string"; break;
      default:
        break;
    }
    f_out("%s%s%s%s%s%s%s%s",
          tab,
          s?"-":"", s?s:"", (s&&l)?"|":"",
          l?"--":"",l?l:"",
          valLbl ? "=" : "", valLbl);
    if(f->helpText){
      f_out("\n%s%s%s", tab, tab, f->helpText);
    }
    f_out("\n\n");
  }
}

void fcli_help(void){
  if(fcli.appHelp){
    if(fcli.appHelp->briefUsage){
      f_out("Usage: %s [options] %s\n", fcli.appName, fcli.appHelp->briefUsage);
    }
    if(fcli.appHelp->briefDescription){
      f_out("\n%s\n", fcli.appHelp->briefDescription);
    }
  }else{
    f_out("Help for %s:\n", fcli.appName);
  }
  const int helpCount = fcli.transient.helpRequested
    + fcli.clientFlags.verbose;
  bool const showGlobal = helpCount>1;
  bool const showApp = (2!=helpCount);
  if(showGlobal){
    f_out("\nFCli global flags:\n\n");
    fcli_cliflag_help(FCliFlagsGlobal);
  }else{
    f_out("\n");
  }
  if(showApp){
    if(FCliHelpState.flags
       && (FCliHelpState.flags[0].flagShort || FCliHelpState.flags[0].flagLong)){
      f_out("App-specific flags:\n\n");
      fcli_cliflag_help(FCliHelpState.flags);
      //f_out("\n");
    }
    if(fcli.appHelp && fcli.appHelp->callback){
      fcli.appHelp->callback();
      f_out("\n");
    }
  }
  if(showGlobal){
    if(!showApp){
      f_out("Invoke --help three times to list "
            "both the framework- and app-level options.\n");
    }else{
      f_out("Invoke --help once to list only the "
            "app-level flags.\n");
    }
  }else{
    f_out("Invoke --help twice to list the framework-level "
          "options. Use --help three times to list both "
          "framework- and app-level options.\n");
  }
  f_out("\nFlags which require values may be passed as "
        "--flag=value or --flag value.\n\n");
}

int fcli_process_flags( fcli_cliflag const * defs ) {
  fcli_cliflag const * f;
  int rc = 0;
  /**
     TODO/FIXME/NICE-TO-HAVE: we "really should" process the CLI flags
     in the order they are provided on the CLI, as opposed to the
     order they're defined in the defs array. The current approach is
     much simpler to process but keeps us from being able to support
     certain useful flag-handling options, e.g.:

     f-tag -a artifact-id-1 --tag x=y --tag y=z -a artifact-id-2 --tag a=b...

     The current approach consumes the -a flags first, leaving us
     unable to match the --tag flags to their corresponding
     (left-hand) -a flag.

     Processing them the other way around, however, requires that we
     keep track of which flags we've already seen so that we can
     reject, where appropriate, duplicate invocations.

     We could, instead of looping on the defs array, loop over the
     head of fcli.argv. If it's a non-flag, move it out of the way
     temporarily (into a new list), else look over the defs array
     looking for a flag match. We don't know, until finding such a
     match, whether the current flag requires a value. If it does, we
     then have to check the current fcli.argv entry to see if it has a
     value (--x=y) or whether the next argv entry is its value (--x
     y). If the current tip has no matching defs entry, we have no
     choice but to skip over it in the hopes that the user can use
     fcli_flag() and friends to consume it, but we cannot know, from
     here, whether such a stray flag requires a value, which means we
     cannot know, for sure, how to process the _next_ argument. The
     best we could do is have a heuristic like "if it starts with a
     dash, assume it's a flag, otherwise assume it's a value for the
     previous flag and skip over it," but whether or not that's sane
     enough for daily use is as yet undetermined.

     If we change the CLI interface to require --flag=value for all
     flags, as opposed to optionally allowing (--flag value), the
     above becomes simpler, but CLI usage suffers. Hmmm. e.g.:

     f-ci -m="message" ...

     simply doesn't fit the age-old muscle memory of:

     svn ci -m ...
     cvs ci -m ...
     fossil ci -m ...
  */
  for( f = defs; f->flagShort || f->flagLong; ++f ){
    if(!f->flagValue && !f->callback){
      /* We accept these for purposes of generating the --help text,
         but we can't otherwise do anything sensible with them and
         assume the app will handle such flags downstream or ignore
         them altogether.*/
      continue;
    }
    char const * v = NULL;
    const char ** passV = f->flagValue ? &v : NULL;
    switch(f->flagType){
      case FCLI_FLAG_TYPE_BOOL:
      case FCLI_FLAG_TYPE_BOOL_INVERT:
        passV = NULL;
        break;
      default: break;
    };
    bool const gotIt = fcli_flag2(f->flagShort, f->flagLong, passV);
    if(fcli__error->code){
      /**
         Corner case. Consider:

         FCLI_FLAG("x","y","xy", &foo, "blah");

         And: my-app -x

         That will cause fcli_flag2() to return false, but it will
         also populate fcli__error for us.
      */
      rc = fcli__error->code;
      break;
    }
    //MARKER(("Got?=%d flag: %s/%s %s\n",gotIt, f->flagShort, f->flagLong, v ? v : ""));
    if(!gotIt){
      continue;
    }
    assert(f->flagValue || f->callback);
    if(f->flagValue) switch(f->flagType){
      case FCLI_FLAG_TYPE_BOOL:
        *((bool*)f->flagValue) = true;
        break;
      case FCLI_FLAG_TYPE_BOOL_INVERT:
        *((bool*)f->flagValue) = false;
        break;
      case FCLI_FLAG_TYPE_CSTR:
        if(!v) goto missing_val;
        *((char const **)f->flagValue) = v;
        break;
      case FCLI_FLAG_TYPE_INT32:
        if(!v) goto missing_val;
        *((int32_t*)f->flagValue) = atoi(v);
        break;
      case FCLI_FLAG_TYPE_INT64:
        if(!v) goto missing_val;
        *((int64_t*)f->flagValue) = atoll(v);
        break;
      case FCLI_FLAG_TYPE_ID:
        if(!v) goto missing_val;
        if(sizeof(fsl_id_t)>32){
          *((fsl_id_t*)f->flagValue) = (fsl_id_t)atoll(v);
        }else{
          *((fsl_id_t*)f->flagValue) = (fsl_id_t)atol(v);
        }
        break;
      case FCLI_FLAG_TYPE_DOUBLE:
        if(!v) goto missing_val;
        *((double*)f->flagValue) = strtod(v, NULL);
        break;
      default:
        MARKER(("As-yet-unhandled flag type for flag %s%s%s.",
                f->flagShort ? f->flagShort : "",
                (f->flagShort && f->flagLong) ? "|" : "",
                f->flagLong ? f->flagLong : ""));
        rc = FSL_RC_MISUSE;
        break;
    }
    if(rc) break;
    else if(f->callback){
      rc = f->callback(f);
      if(rc==FCLI_RC_FLAG_AGAIN){
        rc = 0;
        --f;
      }else if(rc){
        break;
      }
    }
  }
  //MARKER(("fcli__error->code==%s\n", fsl_rc_cstr(fcli__error->code)));
  return rc;
  missing_val:
  rc = fcli_err_set(FSL_RC_MISUSE,"Missing value for flag %s%s%s.",
                    f->flagShort ? f->flagShort : "",
                    (f->flagShort && f->flagLong) ? "|" : "",
                    f->flagLong ? f->flagLong : "");
  return rc;
}

/**
   oldMode must be true if fcli.cliFlags is NULL, else false.
*/
static int fcli_process_argv( bool oldMode, int argc, char const * const * argv ){
  int i;
  int rc = 0;
  char * cp;
  fcli.appName = argv[0];
  fcli.argc = 0;
  fcli.argv = (char **)fsl_malloc( (argc + 1) * sizeof(char*));
  fcli.argv[argc] = NULL;
  for( i = 1; i < argc; ++i ){
    char const * arg = argv[i];
    if('-'==*arg){
      char const * flag = arg+1;
      while('-'==*flag) ++flag;
#define FLAG(F) if(0==fsl_strcmp(F,flag))
      if(oldMode){
        FLAG("help") {
          ++fcli.transient.helpRequested;
          continue;
        }
        FLAG("?") {
          ++fcli.transient.helpRequested;
          continue;
        }
        FLAG("V") {
          fcli.clientFlags.verbose += 1;
          continue;
        }
        FLAG("VV") {
          fcli.clientFlags.verbose += 2;
          continue;
        }
        FLAG("VVV") {
          fcli.clientFlags.verbose += 3;
          continue;
        }
        FLAG("verbose") {
          fcli.clientFlags.verbose += 1;
          continue;
        }
      }
#undef FLAG
      /* else fall through */
    }
    cp = fsl_strdup(arg);
    if(!cp) return FSL_RC_OOM;
    fcli.argv[fcli.argc++] = cp;
    fcli_fax(cp);
  }
  if(!rc && !oldMode){
    rc = fcli_process_flags(FCliFlagsGlobal);
  }
  return rc;
}

bool fcli_flag(char const * opt, const char ** value){
  int i = 0;
  int remove = 0 /* number of items to remove from argv */;
  bool rc = false /* true if found, else 0 */;
  fsl_size_t optLen = fsl_strlen(opt);
  for( ; i < fcli.argc; ++i ){
    char const * arg = fcli.argv[i];
    char const * x;
    char const * vp = NULL;
    if(!arg || ('-' != *arg)) continue;
    rc = false;
    x = arg+1;
    if('-' == *x) { ++x;}
    if(0 != fsl_strncmp(x, opt, optLen)) continue;
    if(!value){
      if(x[optLen]) continue /* not exact match */;
      /* Treat this as a boolean. */
      rc = true;
      ++remove;
      break;
    }else{
      /* -FLAG VALUE or -FLAG=VALUE */
      if(x[optLen] == '='){
        rc = true;
        vp = x+optLen+1;
        ++remove;
      }
      else if(x[optLen]) continue /* not an exact match */;
      else if(i<(fcli.argc-1)){ /* -FLAG VALUE */
        vp = fcli.argv[i+1];
        if('-'==*vp && vp[1]/*allow "-" by itself!*/){
          // VALUE looks like a flag.
          fcli_err_set(FSL_RC_MISUSE, "Missing value for flag [%s].",
                       opt);
          rc = false;
          assert(!remove);
          break;
        }
        rc = true;
        remove += 2;
      }
      else{
        /*
          --FLAG is expecting VALUE but we're at end of argv.  Leave
          --FLAG in the args and report this as "not found."
        */
        rc = false;
        assert(!remove);
        fcli_err_set(FSL_RC_MISUSE,
                     "Missing value for flag [%s].",
                     opt);
        assert(fcli__error->code);
        //MARKER(("Missing flag value for [%s]\n",opt));
        break;
      }
      if(rc){
        *value = vp;
      }
      break;
    }
  }
  if(remove>0){
    int x;
    for( x = 0; x < remove; ++x ){
      fcli.argv[i+x] = NULL/*memory ownership==>FCliFree*/;
    }
    for( ; i < fcli.argc; ++i ){
      fcli.argv[i] = fcli.argv[i+remove];
    }
    fcli.argc -= remove;
    fcli.argv[i] = NULL;
  }
  //MARKER(("flag %s check rc=%s\n",opt,fsl_rc_cstr(fcli__error->code)));
  return rc;
}

bool fcli_flag2(char const * shortOpt,
                char const * longOpt,
                const char ** value){
  bool rc = 0;
  if(shortOpt) rc = fcli_flag(shortOpt, value);
  if(!rc && longOpt && !fcli__error->code) rc = fcli_flag(longOpt, value);
  //MARKER(("flag %s check rc=%s\n",shortOpt,fsl_rc_cstr(fcli__error->code)));
  return rc;
}

bool fcli_flag_or_arg(char const * shortOpt,
                      char const * longOpt,
                      const char ** value){
  bool rc = fcli_flag(shortOpt, value);
  if(!rc && !fcli__error->code){
    rc = fcli_flag(longOpt, value);
    if(!rc && value){
      const char * arg = fcli_next_arg(1);
      if(arg){
        rc = true;
        *value = arg;
      }
    }
  }
  return rc;
}


/**
    We copy fsl_lib_configurable.allocator as a base allocator.
 */
static fsl_allocator fslAllocOrig;

/**
    Proxies fslAllocOrig.f() and abort()s on OOM conditions.
*/
static void * fsl_realloc_f_failing(void * state, void * mem, fsl_size_t n){
  void * rv = fslAllocOrig.f(fslAllocOrig.state, mem, n);
  if(n && !rv){
    fsl_fatal(FSL_RC_OOM, NULL)/*does not return*/;
  }
  return rv;
}

/**
    Replacement for fsl_memory_allocator() which abort()s on OOM.
    Why? Because fossil(1) has shown how much that can simplify error
    checking in an allocates-often API.
 */
static const fsl_allocator fcli_allocator = {
fsl_realloc_f_failing,
NULL/*state*/
};

void fcli_pre_setup(void){
  static int run = 0;
  if(run++) return;
  fslAllocOrig = fsl_lib_configurable.allocator;
  fsl_lib_configurable.allocator = fcli_allocator
    /* This MUST be done BEFORE the fsl API allocates
       ANY memory! */;
  atexit(fcli_shutdown);
}
/**
   oldMode must be true if fcli.cliFlags is NULL, else false.
*/
static int fcli_setup_common1(bool oldMode, int argc, char const * const *argv){
  static char once = 0;
  int rc = 0;
  if(once++){
    fprintf(stderr,"MISUSE: fcli_setup() must "
            "not be called more than once.");
    return FSL_RC_MISUSE;
  }
  fsl_timer_start(&fcliTimer);
  fcli_pre_setup();
  rc = fcli_process_argv(oldMode, argc, argv);
  if(!rc && fcli.argc && 0==fsl_strcmp("help",fcli.argv[0])){
    fcli_next_arg(1) /* strip argument */;
    ++fcli.transient.helpRequested;
  }
  return rc;
}

static int fcli_setup_common2(void){
  int rc = 0;
  fsl_cx_init_opt init = fsl_cx_init_opt_empty;
  fsl_cx * f = 0;

  init.config.sqlPrint = 1;
  if(fcli.config.outputer.out){
    init.output = fcli.config.outputer;
    fcli.config.outputer = fsl_outputer_empty
      /* To avoid any confusion about ownership */;
  }else{
    init.output = fsl_outputer_FILE;
    init.output.state.state = stdout;
  }
  if(fcli.config.traceSql>0 || TempFlags.traceSql){
    init.config.traceSql = fcli.config.traceSql;
  }
    
  rc = fsl_cx_init( &f, &init );
  fcli.f = f;
#if 0
  /* Just for testing cache size effects... */
  f->cache.arty.szLimit = 1024 * 1024 * 20;
  f->cache.arty.usedLimit = 300;
#endif
  fsl_error_clear(&fcli.err);
  FCLI_V3(("Initialized fsl_cx @0x%p. rc=%s\n",
           (void const *)f, fsl_rc_cstr(rc)));
  if(!rc){
#if 0
    if(fcli.transient.gmtTime){
      fsl_cx_flag_set(f, FSL_CX_F_LOCALTIME_GMT, 1);
    }
#endif
    if(fcli.clientFlags.checkoutDir || fcli.transient.repoDbArg){
      rc = fcli_open();
      FCLI_V3(("fcli_open() rc=%s\n", fsl_rc_cstr(rc)));
      if(!fcli.transient.repoDbArg && fcli.clientFlags.checkoutDir
         && (FSL_RC_NOT_FOUND == rc)){
        /* If [it looks like] we tried an implicit checkout-open but
           didn't find one, suppress the error. */
        rc = 0;
        fcli_err_reset();
      }
    }
  }
  if(!rc){
    char const * userName = fcli.transient.userArg;
    if(userName){
      fsl_cx_user_set(f, userName);
    }else if(!fsl_cx_user_get(f)){
      char * u = fsl_guess_user_name();
      fsl_cx_user_set(f, u);
      fsl_free(u);
    }
  }
  return rc;
}

static int fcli_setup2(int argc, char const * const * argv,
                       const fcli_cliflag * flags){
  int rc;
  FCliHelpState.flags = flags;
  rc = fcli_setup_common1(false, argc, argv);
  if(rc) return rc;
  assert(!fcli__error->code);
  if(fcli.transient.helpRequested){
    /* Do this last so that we can get the default user name and such
       for display in the help text. */
    fcli_help();
    rc = FCLI_RC_HELP;
  }else{
    rc = fcli_process_flags(flags);
    if(rc) assert(fcli__error->msg.used);
    if(!rc){
      rc = fcli_setup_common2();
    }
  }
  return rc;
}

int fcli_setup(int argc, char const * const * argv ){
  int rc = 0;
  if(fcli.cliFlags){
    return fcli_setup2(argc, argv, fcli.cliFlags);
  }
  rc = fcli_setup_common1(true, argc, argv);
  if(!rc){
    //f_out("fcli.transient.helpRequested=%d\n",fcli.transient.helpRequested);
    if(fcli.transient.helpRequested){
      /* Do this last so that we can get the default user name and such
         for display in the help text. */
      fcli_help();
      rc = FCLI_RC_HELP;
    }else{
      if( fcli_flag2("C", "no-checkout", NULL) ){
        fcli.clientFlags.checkoutDir = NULL;
      }
      fcli_flag2("U","user", &fcli.transient.userArg);
      fcli.config.traceSql =  fcli_flag2("S","trace-sql", NULL);
      fcli.clientFlags.dryRun = fcli_flag2("D","dry-run", NULL);
      fcli_flag2("R", "repo", &fcli.transient.repoDbArg);
      rc = fcli_setup_common2();
    }
  }
  return rc;
}


int fcli_err_report2(bool clear, char const * file, int line){
  int errRc = 0;
  char const * msg = NULL;
  errRc = fsl_error_get( fcli__error, &msg, NULL );
  if(FCLI_RC_HELP==errRc){
    errRc = 0;
  }else if(errRc || msg){
    if(fcli.clientFlags.verbose>0){
      fcli_printf("%s %s:%d: ERROR #%d (%s): %s\n",
                  fcli.appName,
                  file, line, errRc, fsl_rc_cstr(errRc), msg);
    }else{
      fcli_printf("%s: ERROR #%d (%s): %s\n",
                  fcli.appName, errRc, fsl_rc_cstr(errRc), msg);
    }
  }
  if(clear) fcli_err_reset();
  return errRc;
}


const char * fcli_next_arg(bool remove){
  const char * rc = (fcli.argc>0) ? fcli.argv[0] : NULL;
  if(rc && remove){
    int i;
    --fcli.argc;
    for(i = 0; i < fcli.argc; ++i){
      fcli.argv[i] = fcli.argv[i+1];
    }
    fcli.argv[fcli.argc] = NULL/*owned by FCliFree*/;
  }
  return rc;
}

int fcli_has_unused_args(bool outputError){
  int rc = 0;
  if(fcli.argc){
    rc = fsl_cx_err_set(fcli.f, FSL_RC_MISUSE,
                        "Unhandled extra argument: %s",
                        fcli.argv[0]);
    if(outputError){
      fcli_err_report(false);
    }
  }
  return rc;
}
int fcli_has_unused_flags(bool outputError){
  int i;
  for( i = 0; i < fcli.argc; ++i ){
    char const * arg = fcli.argv[i];
    if('-'==*arg){
      int rc = fsl_cx_err_set(fcli.f, FSL_RC_MISUSE,
                              "Unhandled/unknown flag or missing value: %s",
                              arg);
      if(outputError){
        fcli_err_report(false);
      }
      return rc;
    }
  }
  return 0;
}

int fcli_err_set(int code, char const * fmt, ...){
  int rc;
  va_list va;
  va_start(va, fmt);
  rc = fsl_error_setv(fcli__error, code, fmt, va);
  va_end(va);
  return rc;
}

int fcli_end_of_main(int mainRc){
  if(FCLI_RC_HELP==mainRc){
    mainRc = 0;
  }
  if(fcli_err_report(true)){
    return EXIT_FAILURE;
  }else if(mainRc){
    fcli_err_set(mainRc,"Ending with unadorned end-of-app "
                 "error code %d/%s.",
                 mainRc, fsl_rc_cstr(mainRc));
    fcli_err_report(true);
    return EXIT_FAILURE;
  }
  return EXIT_SUCCESS;
}

int fcli_dispatch_commands( fcli_command const * cmd,
                            bool reportErrors){
  int rc = 0;
  const char * arg = fcli_next_arg(0);
  fcli_command const * orig = cmd;
  fcli_command const * helpPos = 0;
  int helpState = 0;
  if(!arg){
    return fcli_err_set(FSL_RC_MISUSE,
                        "Missing command argument. Try --help.");
  }
  assert(fcli.f);
  for(; arg && cmd->name; ++cmd ){
    if(cmd==orig && 0==fsl_strcmp(arg,"help")){
      /* Accept either (help command) or (command help) as help. */
      /* Except that it turns out that fcli_setup() will trump the
         former and doesn't have the fcli_command state, so can't do
         this. Maybe we can change that somehow. */
      helpState = 1;
      helpPos = orig;
      arg = fcli_next_arg(1); // consume it
    }else if(0==fsl_strcmp(arg,cmd->name)){
      if(!cmd->f){
        rc = fcli_err_set(FSL_RC_NYI,
                               "Command [%s] has no "
                               "callback function.");
      }else{
        fcli_next_arg(1)/*consume it*/;
        if(helpState){
          assert(1==helpState);
          helpState = 2;
          helpPos = cmd;
          break;
        }
        const char * helpCheck = fcli_next_arg(false);
        if(helpCheck && 0==fsl_strcmp("help",helpCheck)){
          helpState = 3;
          helpPos = cmd;
          break;
        }else{
          rc = cmd->f(cmd);
        }
      }
      break;
    }
  }
  if(helpState){
    f_out("\n");
    fcli_command_help(helpPos, helpState>1);
  }else if(!cmd->name){
    fsl_buffer msg = fsl_buffer_empty;
    int rc2;
    if(!arg){
      rc2 = FSL_RC_MISUSE;
      fsl_buffer_appendf(&msg, "No command provided.");
    }else{
      rc2 = FSL_RC_NOT_FOUND;
      fsl_buffer_appendf(&msg, "Command not found: %s.",arg);
    }
    fsl_buffer_appendf(&msg, " Available commands: ");
    cmd = orig;
    for( ; cmd && cmd->name; ++cmd ){
      fsl_buffer_appendf( &msg, "%s%s",
                          (cmd==orig) ? "" : ", ",
                          cmd->name);
    }
    rc = fcli_err_set(rc2, "%b", &msg);
    fsl_buffer_clear(&msg);
  }
  if(rc && reportErrors){
    fcli_err_report(0);
  }
  return rc;
}

void fcli_command_help(fcli_command const * cmd, bool onlyOne){
  fcli_command const * c = cmd;
  for( ; c->name; ++c ){
    f_out("[%s] command:\n\n", c->name);
    if(c->briefDescription){
      f_out("  %s\n", c->briefDescription);
    }
    if(c->flags){
      f_out("\n");
      fcli_cliflag_help(c->flags);
    }
    if(onlyOne) break;
  }
}

void fcli_fax(void * mem){
  if(mem){
    fsl_list_append( &FCliFree.list, mem );
  }
}

int fcli_ckout_show_info(bool useUtc){
  fsl_cx * const f = fcli_cx();
  int rc = 0;
  fsl_stmt st = fsl_stmt_empty;
  fsl_db * const dbR = fsl_cx_db_repo(f);
  fsl_db * const dbC = fsl_cx_db_ckout(f);
  int lblWidth = -20;
  if(!fsl_needs_ckout(f)){
    return FSL_RC_NOT_A_CKOUT;
  }
  assert(dbR);
  assert(dbC);

  fsl_id_t rid = 0;
  fsl_uuid_cstr uuid = NULL;
  fsl_ckout_version_info(f, &rid, &uuid);
  assert((uuid && (rid>0)) || (!uuid && (0==rid)));

  f_out("%*s %s\n", lblWidth, "repository-db:",
        fsl_cx_db_file_repo(f, NULL));
  f_out("%*s %s\n", lblWidth, "checkout-root:",
        fsl_cx_ckout_dir_name(f, NULL));

  rc = fsl_db_prepare(dbR, &st, "SELECT "
                      /*0*/"datetime(event.mtime%s) AS timestampString, "
                      /*1*/"coalesce(euser, user) AS user, "
                      /*2*/"(SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref "
                      "WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid "
                      "AND tagxref.rid=blob.rid AND tagxref.tagtype>0) as tags, "
                      /*3*/"coalesce(ecomment, comment) AS comment, "
                      /*4*/"uuid AS uuid "
                      "FROM event JOIN blob "
                      "WHERE "
                      "event.type='ci' "
                      "AND blob.rid=%"FSL_ID_T_PFMT" "
                      "AND blob.rid=event.objid "
                      "ORDER BY event.mtime DESC",
                      useUtc ? "" : ", 'localtime'",
                      rid);
  if(rc) goto dberr;
  if( FSL_RC_STEP_ROW != fsl_stmt_step(&st)){
    /* fcli_err_set(FSL_RC_ERROR, "Event data for checkout not found."); */
    f_out("\nNo 'event' data found. This is only normal for an empty repo.\n");
    goto end;

  }

  f_out("%*s %s %s %s (RID %"FSL_ID_T_PFMT")\n",
        lblWidth, "checkout-version:",
        fsl_stmt_g_text(&st, 4, NULL),
        fsl_stmt_g_text(&st, 0, NULL),
        useUtc ? "UTC" : "local",
        rid );

  {
    /* list parent(s) */
    fsl_stmt stP = fsl_stmt_empty;
    rc = fsl_db_prepare(dbR, &stP, "SELECT "
                        "uuid, pid, isprim "
                        "FROM plink JOIN blob ON pid=rid "
                        "WHERE cid=%"FSL_ID_T_PFMT" "
                        "ORDER BY isprim DESC, mtime DESC /*sort*/",
                        rid);
    if(rc) goto dberr;
    while( FSL_RC_STEP_ROW == fsl_stmt_step(&stP) ){
      char const * zLabel = fsl_stmt_g_int32(&stP,2)
        ? "parent:" : "merged-from:";
      f_out("%*s %s\n", lblWidth, zLabel,
            fsl_stmt_g_text(&stP, 0, NULL));
      
    }
    fsl_stmt_finalize(&stP);
  }
  {
    /* list children */
    fsl_stmt stC = fsl_stmt_empty;
    rc = fsl_db_prepare(dbR, &stC, "SELECT "
                        "uuid, cid, isprim "
                        "FROM plink JOIN blob ON cid=rid "
                        "WHERE pid=%"FSL_ID_T_PFMT" "
                        "ORDER BY isprim DESC, mtime DESC /*sort*/",
                        rid);
    if(rc) goto dberr;
    while( FSL_RC_STEP_ROW == fsl_stmt_step(&stC) ){
      char const * zLabel = fsl_stmt_g_int32(&stC,2)
        ? "child:" : "merged-into:";
      f_out("%*s %s\n", lblWidth, zLabel,
            fsl_stmt_g_text(&stC, 0, NULL));
      
    }
    fsl_stmt_finalize(&stC);
  }

  f_out("%*s %s\n", lblWidth, "user:",
        fsl_stmt_g_text(&st, 1, NULL));

  f_out("%*s %s\n", lblWidth, "tags:",
        fsl_stmt_g_text(&st, 2, NULL));

  f_out("%*s %s\n", lblWidth, "comment:",
        fsl_stmt_g_text(&st, 3, NULL));

  dberr:
  if(rc){
    fsl_cx_uplift_db_error(f, dbR);
  }
  end:
  fsl_stmt_finalize(&st);

  return rc;
}

static int fsl_stmt_each_f_ambiguous( fsl_stmt * stmt, void * state ){
  int rc;
  if(1==stmt->rowCount) stmt->rowCount=0
                          /* HORRIBLE KLUDGE to elide header. */;
  rc = fsl_stmt_each_f_dump(stmt, state);
  if(0==stmt->rowCount) stmt->rowCount = 1;
  return rc;
}

void fcli_list_ambiguous_artifacts(char const * label,
                                   char const *prefix){
  fsl_db * const db = fsl_cx_db_repo(fcli.f);
  assert(db);
  if(!label){
    f_out("Artifacts matching ambiguous prefix: %s\n",prefix);
  }else if(*label){
    f_out("%s\n", label);
  }  
  /* Possible fixme? Do we only want to list checkins
     here? */
  int rc = fsl_db_each(db, fsl_stmt_each_f_ambiguous, 0,
              "SELECT uuid, CASE "
              "WHEN type='ci' THEN 'Checkin' "
              "WHEN type='w'  THEN 'Wiki' "
              "WHEN type='g'  THEN 'Control' "
              "WHEN type='e'  THEN 'Technote' "
              "WHEN type='t'  THEN 'Ticket' "
              "WHEN type='f'  THEN 'Forum' "
              "ELSE '?'||'?'||'?' END " /* '???' ==> trigraph! */
              "FROM blob b, event e WHERE uuid LIKE %Q||'%%' "
              "AND b.rid=e.objid "
              "ORDER BY uuid",
              prefix);
  if(rc){
    fsl_cx_uplift_db_error(fcli.f, db);
    fcli_err_report(false);
  }
}

fsl_db * fcli_db_ckout(void){
  return fcli.f ? fsl_cx_db_ckout(fcli.f) : NULL;
}

fsl_db * fcli_db_repo(void){
  return fcli.f ? fsl_cx_db_repo(fcli.f) : NULL;
}

fsl_db * fcli_needs_ckout(void){
  if(fcli.f) return fsl_needs_ckout(fcli.f);
  fcli_err_set(FSL_RC_NOT_A_CKOUT,
               "No checkout db is opened.");
  return NULL;
}

fsl_db * fcli_needs_repo(void){
  if(fcli.f) return fsl_needs_repo(fcli.f);
  fcli_err_set(FSL_RC_NOT_A_REPO,
               "No repository db is opened.");
  return NULL;
}

int fcli_args_to_vfile_ids(fsl_id_bag *tgt, fsl_id_t vid,
                           bool relativeToCwd,
                           bool changedFilesOnly){
  if(!fcli.argc){
    return fcli_err_set(FSL_RC_MISUSE,
                        "No file/dir name arguments provided.");
  }
  int rc = 0;
  char const * zName;
  while( !rc && (zName = fcli_next_arg(true))){
    FCLI_V3(("Collecting vfile ID(s) for: %s\n", zName));
    rc = fsl_ckout_vfile_ids(fcli.f, vid, tgt, zName,
                             relativeToCwd, changedFilesOnly);
  }
  return rc;
}

int fcli_fingerprint_check(bool reportImmediately){
  int rc = fsl_ckout_fingerprint_check(fcli.f);
  if(rc && reportImmediately){
    f_out("ERROR: repo/checkout fingerprint mismatch detected. "
          "To recover from this, (fossil close) the current checkout, "
          "then re-open it. Be sure to store any modified files somewhere "
          "safe and restore them after re-opening the repository.\n");
  }
  return rc;
}

#undef FCLI_V3
#undef fcli_empty_m
#undef fcli__error
#undef MARKER
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<












































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/config.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 implements (most of) the fsl_xxx APIs related to handling
  configuration data from the db(s).
*/
#include "fossil-scm/fossil-internal.h"
#include "fossil-scm/fossil-confdb.h"
#include <assert.h>
#include <stdlib.h> /* bsearch() */
#define MARKER(pfexp)                                               \
  do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__);   \
    printf pfexp;                                                   \
  } while(0)

/**
   File-local macro used to ensure that all cached statements used by
   the fsl_config_get_xxx() APIs use an equivalent string (so that
   they use the same underlying cache fsl_stmt handle).  The first %s
   represents one of the config table names (config, vvfar,
   global_config). Normally we wouldn't use %s in a cached statement,
   but we're only expecting 3 values for table here and each one will
   only be cached once. The 2nd %s must be __FILE__.
*/
#define SELECT_FROM_CONFIG "SELECT value FROM %s WHERE name=?/*%s*/"

char const * fsl_config_table_for_role(fsl_confdb_e mode){
  switch(mode){
    case FSL_CONFDB_REPO: return "config";
    case FSL_CONFDB_CKOUT: return "vvar";
    case FSL_CONFDB_GLOBAL: return "global_config";
    case FSL_CONFDB_VERSIONABLE: return NULL;
    default:
      assert(!"Invalid fsl_confdb_e value");
      return NULL;
  }
}

fsl_db * fsl_config_for_role(fsl_cx * f, fsl_confdb_e mode){
  switch(mode){
    case FSL_CONFDB_REPO: return fsl_cx_db_repo(f);
    case FSL_CONFDB_CKOUT: return fsl_cx_db_ckout(f);
    case FSL_CONFDB_GLOBAL: return fsl_cx_db_config(f);
    case FSL_CONFDB_VERSIONABLE: return fsl_cx_db(f);
    default:
      assert(!"Invalid fsl_confdb_e value");
      return NULL;
  }
}

int fsl_config_versionable_filename(fsl_cx *f, char const * key,
                                    fsl_buffer *b){
  if(!f || !fsl_needs_ckout(f)) return FSL_RC_NOT_A_CKOUT;
  else if(!key || !*key || !fsl_is_simple_pathname(key, true)){
    return FSL_RC_MISUSE;
  }
  fsl_buffer_reuse(b);
  return fsl_buffer_appendf(b, "%s.fossil-settings/%s",
                            f->ckout.dir, key);
}


int fsl_config_unset( fsl_cx * f, fsl_confdb_e mode, char const * key ){
  fsl_db * db = fsl_config_for_role(f, mode);
  if(!db || !key || !*key) return FSL_RC_MISUSE;
  else if(mode==FSL_CONFDB_VERSIONABLE) return FSL_RC_UNSUPPORTED;
  else{
    char const * table = fsl_config_table_for_role(mode);
    assert(table);
    return fsl_db_exec(db, "DELETE FROM %s WHERE name=%Q", table, key);
  }
}

int32_t fsl_config_get_int32( fsl_cx * f, fsl_confdb_e mode,
                              int32_t dflt, char const * key ){
  int32_t rv = dflt;
  switch(mode){
    case FSL_CONFDB_VERSIONABLE:{
      char * val = fsl_config_get_text(f, mode, key, NULL);
      if(val){
        rv = (int32_t)atoi(val);
        fsl_free(val);
      }
      break;
    }
    default: {
      fsl_db * db = fsl_config_for_role(f, mode);
      char const * table = fsl_config_table_for_role(mode);
      assert(table);
      if(db){
        fsl_stmt * st = NULL;
        fsl_db_prepare_cached(db, &st, SELECT_FROM_CONFIG,
                              table, __FILE__);
        if(st){
          fsl_stmt_bind_text(st, 1, key, -1, 0);
          if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
            rv = fsl_stmt_g_int32(st, 0);
          }
          fsl_stmt_cached_yield(st);
        }
      }
      break;
    }
  }
  return rv;
}

int64_t fsl_config_get_int64( fsl_cx * f, fsl_confdb_e mode,
                              int64_t dflt, char const * key ){
  int64_t rv = dflt;
  switch(mode){
    case FSL_CONFDB_VERSIONABLE:{
      char * val = fsl_config_get_text(f, mode, key, NULL);
      if(val){
        rv = (int64_t)strtoll(val, NULL, 10);
        fsl_free(val);
      }
      break;
    }
    default: {
      fsl_db * db = fsl_config_for_role(f, mode);
      char const * table = fsl_config_table_for_role(mode);
      assert(table);
      if(db){
        fsl_stmt * st = NULL;
        fsl_db_prepare_cached(db, &st, SELECT_FROM_CONFIG,
                              table, __FILE__);
        if(st){
          fsl_stmt_bind_text(st, 1, key, -1, 0);
          if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
            rv = fsl_stmt_g_int64(st, 0);
          }
          fsl_stmt_cached_yield(st);
        }
      }
      break;
    }
  }
  return rv;
}

fsl_id_t fsl_config_get_id( fsl_cx * f, fsl_confdb_e mode,
                            fsl_id_t dflt, char const * key ){
  return (sizeof(fsl_id_t)==sizeof(int32_t))
    ? (fsl_id_t)fsl_config_get_int32(f, mode, dflt, key)
    : (fsl_id_t)fsl_config_get_int64(f, mode, dflt, key);
}

double fsl_config_get_double( fsl_cx * f, fsl_confdb_e mode,
                              double dflt, char const * key ){
  double rv = dflt;
  switch(mode){
    case FSL_CONFDB_VERSIONABLE:{
      char * val = fsl_config_get_text(f, mode, key, NULL);
      if(val){
        rv = strtod(val, NULL);
        fsl_free(val);
      }
      break;
    }
    default: {
      fsl_db * db = fsl_config_for_role(f, mode);
      if(!db) break/*e.g. global config is not opened*/;
      fsl_stmt * st = NULL;
      char const * table = fsl_config_table_for_role(mode);
      assert(table);
      fsl_db_prepare_cached(db, &st, SELECT_FROM_CONFIG,
                            table, __FILE__);
      if(st){
        fsl_stmt_bind_text(st, 1, key, -1, 0);
        if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
          rv = fsl_stmt_g_double(st, 0);
        }
        fsl_stmt_cached_yield(st);
      }
      break;
    }
  }
  return rv;
}

char * fsl_config_get_text( fsl_cx * f, fsl_confdb_e mode,
                            char const * key, fsl_size_t * len ){
  char * rv = NULL;
  fsl_buffer val = fsl_buffer_empty;
  if(fsl_config_get_buffer(f, mode, key, &val)){
    fsl_cx_err_reset(f);
    if(len) *len = 0;
    fsl_buffer_clear(&val)/*in case of partial read failure*/;
  }else{
    if(len) *len = val.used;
    rv = fsl_buffer_take(&val);
  }
  return rv;
}

int fsl_config_get_buffer( fsl_cx * f, fsl_confdb_e mode,
                           char const * key, fsl_buffer * b ){
  int rc = FSL_RC_NOT_FOUND;
  switch(mode){
    case FSL_CONFDB_VERSIONABLE:{
      if(!fsl_needs_ckout(f)){
        rc = FSL_RC_NOT_A_CKOUT;
        break;
      }
      fsl_buffer * fname = fsl_cx_scratchpad(f);
      rc = fsl_config_versionable_filename(f, key, fname);
      if(!rc){
        char const * zFile = fsl_buffer_cstr(fname);
        rc = fsl_stat(zFile, 0, false);
        if(rc){
          rc = fsl_cx_err_set(f, rc, "Could not stat file: %s",
                              zFile);
        }else{
          rc = fsl_buffer_fill_from_filename(b, zFile);
        }
      }
      fsl_cx_scratchpad_yield(f,fname);
      break;
    }
    default: {
      char const * table = fsl_config_table_for_role(mode);
      assert(table);
      fsl_db * const db = fsl_config_for_role(f, mode);
      if(!db) break;
      fsl_stmt * st = NULL;
      rc = fsl_db_prepare_cached(db, &st, SELECT_FROM_CONFIG,
                                 table, __FILE__);
      if(rc){
        rc = fsl_cx_uplift_db_error2(f, db, rc);
        break;
      }
      fsl_stmt_bind_text(st, 1, key, -1, 0);
      if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
        fsl_size_t len = 0;
        char const * s = fsl_stmt_g_text(st, 0, &len);
        rc = s ? fsl_buffer_append(b, s, len) : 0;
      }else{
        rc = FSL_RC_NOT_FOUND;
      }
      fsl_stmt_cached_yield(st);
      break;
    }
  }
  return rc;
}

bool fsl_config_get_bool( fsl_cx * f, fsl_confdb_e mode,
                          bool dflt, char const * key ){
  bool rv = dflt;
  switch(mode){
    case FSL_CONFDB_VERSIONABLE:{
      char * val = fsl_config_get_text(f, mode, key, NULL);
      if(val){
        rv = fsl_str_bool(val);
        fsl_free(val);
      }
      break;
    }
    default:{
      int rc;
      fsl_stmt * st = NULL;
      char const * table = fsl_config_table_for_role(mode);
      fsl_db * db;
      if(!f || !key || !*key) break;
      db = fsl_config_for_role(f, mode);
      if(!db) break;
      assert(table);
      rc = fsl_db_prepare_cached(db, &st, SELECT_FROM_CONFIG,
                                 table, __FILE__);
      if(!rc){
        fsl_stmt_bind_text(st, 1, key, -1, 0);
        if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
          char const * col = fsl_stmt_g_text(st, 0, NULL);
          rv = col ? fsl_str_bool(col) : dflt /* 0? */;
        }
        fsl_stmt_cached_yield(st);
      }
      break;
    }
  }
  return rv;
}

/**
    Sets up a REPLACE statement for the given config db and key. On
    success 0 is returned and *st holds the cached statement. The caller
    must bind() parameter #2 and step() the statement, then
    fsl_stmt_cached_yield() it.
   
    Returns non-0 on error.
*/
static int fsl_config_set_prepare( fsl_cx * f, fsl_stmt **st,
                                   fsl_confdb_e mode, char const * key ){
  char const * table = fsl_config_table_for_role(mode);
  fsl_db * db = fsl_config_for_role(f,mode);
  assert(table);
  if(!db || !key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  else{
    const char * sql = FSL_CONFDB_REPO==mode
      ? "REPLACE INTO %s(name,value,mtime) VALUES(?,?,now())/*%s()*/"
      : "REPLACE INTO %s(name,value) VALUES(?,?)/*%s()*/";
    int rc = fsl_db_prepare_cached(db, st, sql,  table, __func__);
    if(!rc) rc = fsl_stmt_bind_text(*st, 1, key, -1, 1);
    if(rc && !f->error.code){
      fsl_cx_uplift_db_error(f, db);
    }
    return rc;
  }
}


/*
  TODO/FIXME: the fsl_config_set_xxx() routines all use the same basic
  structure, differing only in the concrete bind() op they call. They
  should be consolidated somehow.
*/

/**
   Writes valLen bytes of val to a versioned-setting file. Returns 0
   on success. Requires a checkout db.
*/
static int fsl_config_set_versionable( fsl_cx * f, char const * key,
                                       char const * val,
                                       fsl_size_t valLen){
  assert(key && *key);
  if(!fsl_needs_ckout(f)){
    return FSL_RC_NOT_A_CKOUT;
  }
  fsl_buffer * fName = fsl_cx_scratchpad(f);
  int rc = fsl_config_versionable_filename(f, key, fName);
  if(!rc){
    fsl_buffer fake = fsl_buffer_empty;
    fake.mem = (void*)val;
    fake.capacity = fake.used = valLen;
    rc = fsl_buffer_to_filename(&fake, fsl_buffer_cstr(fName));
  }
  fsl_cx_scratchpad_yield(f, fName);
  return rc;
}

  
int fsl_config_set_text( fsl_cx * f, fsl_confdb_e mode,
                         char const * key, char const * val ){
  if(!key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  else if(FSL_CONFDB_VERSIONABLE==mode){
    return fsl_config_set_versionable(f, key, val,
                                      val ? fsl_strlen(val) : 0);
  }
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db) return FSL_RC_MISUSE;
  fsl_stmt * st = NULL;
  int rc = fsl_config_set_prepare(f, &st, mode, key);
  //MARKER(("config-set mode=%d k=%s v=%s\n",
  //        mode, key, val));
  if(!rc){
    if(val){
      rc = fsl_stmt_bind_text(st, 2, val, -1, 0);
    }else{
      rc = fsl_stmt_bind_null(st, 2);
    }
    if(!rc){
      rc = fsl_stmt_step(st);
    }
    fsl_stmt_cached_yield(st);
    if(FSL_RC_STEP_DONE==rc) rc = 0;
  }
  if(rc && !f->error.code) fsl_cx_uplift_db_error(f, db);
  return rc;
}

int fsl_config_set_blob( fsl_cx * f, fsl_confdb_e mode, char const * key,
                         void const * val, fsl_int_t len ){
  if(!key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  else if(FSL_CONFDB_VERSIONABLE==mode){
    return fsl_config_set_versionable(f, key, val,
                                      (val && len<0)
                                      ? fsl_strlen((char const *)val)
                                      : (fsl_size_t)len);
  }
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db) return FSL_RC_MISUSE;
  fsl_stmt * st = NULL;
  int rc = fsl_config_set_prepare(f, &st, mode, key);
  if(!rc){
    if(val){
      if(len<0) len = fsl_strlen((char const *)val);
      rc = fsl_stmt_bind_blob(st, 2, val, len, 0);
    }else{
      rc = fsl_stmt_bind_null(st, 2);
    }
    if(!rc){
      rc = fsl_stmt_step(st);
    }
    fsl_stmt_cached_yield(st);
    if(FSL_RC_STEP_DONE==rc) rc = 0;
  }
  if(rc && !f->error.code) fsl_cx_uplift_db_error(f, db);
  return rc;
}

int fsl_config_set_int32( fsl_cx * f, fsl_confdb_e mode,
                          char const * key, int32_t val ){
  if(!key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  else if(FSL_CONFDB_VERSIONABLE==mode){
    char buf[64] = {0};
    fsl_snprintf(buf, sizeof(buf), "%" PRIi32 "\n", val);
    return fsl_config_set_versionable(f, key, buf,
                                      fsl_strlen(buf));
  }
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db) return FSL_RC_MISUSE;
  fsl_stmt * st = NULL;
  int rc = fsl_config_set_prepare(f, &st, mode, key);
  if(!rc){
    rc = fsl_stmt_bind_int32(st, 2, val);
    if(!rc){
      rc = fsl_stmt_step(st);
    }
    fsl_stmt_cached_yield(st);
    if(FSL_RC_STEP_DONE==rc) rc = 0;
  }
  if(rc && !f->error.code) fsl_cx_uplift_db_error(f, db);
  return rc;
}

int fsl_config_set_int64( fsl_cx * f, fsl_confdb_e mode,
                          char const * key, int64_t val ){
  if(!key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  else if(FSL_CONFDB_VERSIONABLE==mode){
    char buf[64] = {0};
    fsl_snprintf(buf, sizeof(buf), "%" PRIi64 "\n", val);
    return fsl_config_set_versionable(f, key, buf,
                                      fsl_strlen(buf));
  }
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db) return FSL_RC_MISUSE;
  fsl_stmt * st = NULL;
  int rc = fsl_config_set_prepare(f, &st, mode, key);
  if(!rc){
    rc = fsl_stmt_bind_int64(st, 2, val);
    if(!rc){
      rc = fsl_stmt_step(st);
    }
    fsl_stmt_cached_yield(st);
    if(FSL_RC_STEP_DONE==rc) rc = 0;
  }
  if(rc && !f->error.code) fsl_cx_uplift_db_error(f, db);
  return rc;
}

int fsl_config_set_id( fsl_cx * f, fsl_confdb_e mode,
                          char const * key, fsl_id_t val ){
  if(!key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  else if(FSL_CONFDB_VERSIONABLE==mode){
    char buf[64] = {0};
    fsl_snprintf(buf, sizeof(buf), "%" FSL_ID_T_PFMT "\n", val);
    return fsl_config_set_versionable(f, key, buf,
                                      fsl_strlen(buf));
  }
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db) return FSL_RC_MISUSE;
  fsl_stmt * st = NULL;
  int rc = fsl_config_set_prepare(f, &st, mode, key);
  if(!rc){
    rc = fsl_stmt_bind_id(st, 2, val);
    if(!rc){
      rc = fsl_stmt_step(st);
    }
    fsl_stmt_cached_yield(st);
    if(FSL_RC_STEP_DONE==rc) rc = 0;
  }
  if(rc && !f->error.code) fsl_cx_uplift_db_error(f, db);
  return rc;
}

int fsl_config_set_double( fsl_cx * f, fsl_confdb_e mode,
                           char const * key, double val ){
  if(!key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  else if(FSL_CONFDB_VERSIONABLE==mode){
    char buf[128] = {0};
    fsl_snprintf(buf, sizeof(buf), "%f\n", val);
    return fsl_config_set_versionable(f, key, buf,
                                      fsl_strlen(buf));
  }
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db) return FSL_RC_MISUSE;
  fsl_stmt * st = NULL;
  int rc = fsl_config_set_prepare(f, &st, mode, key);
  if(!rc){
    rc = fsl_stmt_bind_double(st, 2, val);
    if(!rc){
        rc = fsl_stmt_step(st);
    }
    fsl_stmt_cached_yield(st);
    if(FSL_RC_STEP_DONE==rc) rc = 0;
  }
  if(rc && !f->error.code) fsl_cx_uplift_db_error(f, db);
  return rc;
}

int fsl_config_set_bool( fsl_cx * f, fsl_confdb_e mode,
                         char const * key, bool val ){
  if(!key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  char buf[4] = {'o','n','\n','\n'};
  if(!val){
    buf[1] = buf[2] = 'f';
  }
  if(FSL_CONFDB_VERSIONABLE==mode){
    return fsl_config_set_versionable(f, key, buf,
                                      val ? 3 : 4);
  }
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db) return FSL_RC_MISUSE;
  fsl_stmt * st = NULL;
  int rc = fsl_config_set_prepare(f, &st, mode, key);
  if(!rc){
    rc = fsl_stmt_bind_text(st, 2, buf, val ? 2 : 3, false);
    if(!rc){
      rc = fsl_stmt_step(st);
    }
    fsl_stmt_cached_yield(st);
    if(FSL_RC_STEP_DONE==rc) rc = 0;
  }
  if(rc && !f->error.code) fsl_cx_uplift_db_error(f, db);
  return rc;
}

int fsl_config_transaction_begin(fsl_cx * f, fsl_confdb_e mode){
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db) return FSL_RC_MISUSE;
  else{
    int const rc = fsl_db_transaction_begin(db);
    if(rc) fsl_cx_uplift_db_error(f, db);
    return rc;
  }
}

int fsl_config_transaction_end(fsl_cx * f, fsl_confdb_e mode, char rollback){
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db) return FSL_RC_MISUSE;
  else{
    int const rc = fsl_db_transaction_end(db, rollback);
    if(rc) fsl_cx_uplift_db_error(f, db);
    return rc;
  }
}

int fsl_config_globs_load(fsl_cx * f, fsl_list * li, char const * key){
  int rc = 0;
  char * val = NULL;
  if(!f || !li || !key || !*key) return FSL_RC_MISUSE;
  else if(fsl_cx_db_ckout(f)){
    /* Try versionable settings... */
    fsl_buffer buf = fsl_buffer_empty;
    rc = fsl_config_get_buffer(f, FSL_CONFDB_VERSIONABLE, key, &buf);
    if(rc){
      switch(rc){
        case FSL_RC_NOT_FOUND:
          fsl_cx_err_reset(f);
          rc = 0;
          break;
        default:
          fsl_buffer_clear(&buf);
          return rc;
      }
      /* Fall through and try the next option. */
    }else{
      if(buf.mem){
        val = fsl_buffer_take(&buf);
      }else{
        /* Empty but existing list, so it trumps the
           repo/global settings. */;
        fsl_buffer_clear(&buf);
      }
      goto gotone;
    }
  }
  if(fsl_cx_db_repo(f)){
    /* See if the repo can serve us... */
    val = fsl_config_get_text(f, FSL_CONFDB_REPO, key, NULL);
    if(val) goto gotone;
    /* Else fall through and try global config... */
  }
  if(fsl_cx_db_config(f)){
    /*FIXME?: we arguably should open the global config for this if it
      is not already opened.*/
    val = fsl_config_get_text(f, FSL_CONFDB_GLOBAL, key, NULL);
    if(val) goto gotone;
  }
  gotone:
  if(val){
      rc = fsl_glob_list_parse( li, val );
      fsl_free(val);
      val = 0;
      return rc;
  }
  return rc;
}

/*
  TODO???: the infrastructure from fossil's configure.c and db.c which
  deals with the config db and the list of known/allowed settings.

  ==================

static const struct {
  const char *zName;   / * Name of the configuration set * /
  int groupMask;       / * Mask for that configuration set * /
  const char *zHelp;   / * What it does * /

} fslConfigGroups[] = {
  { "/email",        FSL_CONFIGSET_ADDR,  "Concealed email addresses in tickets" },
  { "/project",      FSL_CONFIGSET_PROJ,  "Project name and description"         },
  { "/skin",         FSL_CONFIGSET_SKIN | FSL_CONFIGSET_CSS,
                                      "Web interface appearance settings"    },
  { "/css",          FSL_CONFIGSET_CSS,   "Style sheet"                          },
  { "/shun",         FSL_CONFIGSET_SHUN,  "List of shunned artifacts"            },
  { "/ticket",       FSL_CONFIGSET_TKT,   "Ticket setup",                        },
  { "/user",         FSL_CONFIGSET_USER,  "Users and privilege settings"         },
  { "/xfer",         FSL_CONFIGSET_XFER,  "Transfer setup",                      },
  { "/all",          FSL_CONFIGSET_ALL,   "All of the above"                     },
  {NULL, 0, NULL}
};
 */

/*
   The following is a list of settings that we are willing to
   transfer.
  
   Setting names that begin with an alphabetic characters refer to
   single entries in the CONFIG table.  Setting names that begin with
   "@" are for special processing.
*/
static struct {
  const char *zName;   /* Name of the configuration parameter */
  int groupMask;       /* Which config groups is it part of */
} fslConfigXfer[] = {
  { "css",                    FSL_CONFIGSET_CSS  },

  { "header",                 FSL_CONFIGSET_SKIN },
  { "footer",                 FSL_CONFIGSET_SKIN },
  { "logo-mimetype",          FSL_CONFIGSET_SKIN },
  { "logo-image",             FSL_CONFIGSET_SKIN },
  { "background-mimetype",    FSL_CONFIGSET_SKIN },
  { "background-image",       FSL_CONFIGSET_SKIN },
  { "index-page",             FSL_CONFIGSET_SKIN },
  { "timeline-block-markup",  FSL_CONFIGSET_SKIN },
  { "timeline-max-comment",   FSL_CONFIGSET_SKIN },
  { "timeline-plaintext",     FSL_CONFIGSET_SKIN },
  { "adunit",                 FSL_CONFIGSET_SKIN },
  { "adunit-omit-if-admin",   FSL_CONFIGSET_SKIN },
  { "adunit-omit-if-user",    FSL_CONFIGSET_SKIN },

  { "th1-setup",              FSL_CONFIGSET_ALL },

  { "tcl",                    FSL_CONFIGSET_SKIN|FSL_CONFIGSET_TKT|FSL_CONFIGSET_XFER },
  { "tcl-setup",              FSL_CONFIGSET_SKIN|FSL_CONFIGSET_TKT|FSL_CONFIGSET_XFER },

  { "project-name",           FSL_CONFIGSET_PROJ },
  { "project-description",    FSL_CONFIGSET_PROJ },
  { "manifest",               FSL_CONFIGSET_PROJ },
  { "binary-glob",            FSL_CONFIGSET_PROJ },
  { "clean-glob",             FSL_CONFIGSET_PROJ },
  { "ignore-glob",            FSL_CONFIGSET_PROJ },
  { "keep-glob",              FSL_CONFIGSET_PROJ },
  { "crnl-glob",              FSL_CONFIGSET_PROJ },
  { "encoding-glob",          FSL_CONFIGSET_PROJ },
  { "empty-dirs",             FSL_CONFIGSET_PROJ },
  { "allow-symlinks",         FSL_CONFIGSET_PROJ },

  { "ticket-table",           FSL_CONFIGSET_TKT  },
  { "ticket-common",          FSL_CONFIGSET_TKT  },
  { "ticket-change",          FSL_CONFIGSET_TKT  },
  { "ticket-newpage",         FSL_CONFIGSET_TKT  },
  { "ticket-viewpage",        FSL_CONFIGSET_TKT  },
  { "ticket-editpage",        FSL_CONFIGSET_TKT  },
  { "ticket-reportlist",      FSL_CONFIGSET_TKT  },
  { "ticket-report-template", FSL_CONFIGSET_TKT  },
  { "ticket-key-template",    FSL_CONFIGSET_TKT  },
  { "ticket-title-expr",      FSL_CONFIGSET_TKT  },
  { "ticket-closed-expr",     FSL_CONFIGSET_TKT  },
  { "@reportfmt",             FSL_CONFIGSET_TKT  },

  { "@user",                  FSL_CONFIGSET_USER },

  { "@concealed",             FSL_CONFIGSET_ADDR },

  { "@shun",                  FSL_CONFIGSET_SHUN },

  { "xfer-common-script",     FSL_CONFIGSET_XFER },
  { "xfer-push-script",       FSL_CONFIGSET_XFER },
};

#define ARRAYLEN(X) (sizeof(X)/sizeof(X[0]))
/*
   Return a pointer to a string that contains the RHS of an IN
   operator that will select CONFIG table names that are part of the
   configuration that matches iMatch. The returned string must
   eventually be fsl_free()'d.
*/
char *fsl_config_inop_rhs(int iMask){
  fsl_buffer x = fsl_buffer_empty;
  const char *zSep = "";
  const int n = (int)ARRAYLEN(fslConfigXfer);
  int i;
  int rc = fsl_buffer_append(&x, "(", 1);
  for(i=0; !rc && (i<n); i++){
    if( (fslConfigXfer[i].groupMask & iMask)==0 ) continue;
    if( fslConfigXfer[i].zName[0]=='@' ) continue;
    rc = fsl_buffer_appendf(&x, "%s%Q", zSep, fslConfigXfer[i].zName);
    zSep = ",";
  }
  if(!rc){
    rc = fsl_buffer_append(&x, ")", 1);
  }
  if(rc){
    fsl_buffer_clear(&x);
    assert(!x.mem);
  }else{
    fsl_buffer_resize(&x, x.used);
  }
  return (char *)x.mem;
}


/**
   Holds metadata for fossil-defined configuration settings.

   As of 2021, this library does NOT intend to maintain 100%
   config-entry parity with fossil, as the vast majority of its config
   settings are application-level preferences. The library will
   maintain compatibility with versionable settings and a handful of
   settings which make sense for a library-level API
   (e.g. 'ignore-glob' and 'manifest'). The majority of fossil's
   settings, however, are specific to that application and will not be
   treated specially by the library.

   @see fsl_config_ctrl_get()
   @see fsl_config_has_versionable()
   @see fsl_config_key_is_versionable()
   @see fsl_config_key_default_value()
*/  
struct fsl_config_ctrl {
  /** Name of the setting */
  char const *name;
  /**
     Historical (fossil(1)) internal variable name used by
     db_set(). Not currently used by this impl.
  */
  char const *var;
  /**
     Historical (HTML UI). Width of display.  0 for boolean values.
  */
  int width;
  /**
     Is this setting versionable?
  */
  bool versionable;
  /**
     Default value
  */
  char const *defaultValue;
};
typedef struct fsl_config_ctrl fsl_config_ctrl;

/**
   If key is the name of a fossil-defined config key, this returns
   the fsl_config_ctrl value describing that configuration property,
   else it returns NULL.
*/
FSL_EXPORT fsl_config_ctrl const * fsl_config_ctrl_get(char const * key);

/**
   Returns true if key is the name of a config property
   as defined by fossil(1).
*/
FSL_EXPORT bool fsl_config_key_is_fossil(char const * key);


/**
   Returns true if key is the name of a versionable property, as
   defined by fossil(1). Returning false is NOT a guaranty that fossil
   does NOT define that setting (this library does not track _all_
   settings), but returning true is a a guarantee that it is a
   fossil-defined versionable setting.
*/
FSL_EXPORT bool fsl_config_key_is_versionable(char const * key);

/**
   If key refers to a fossil-defined configuration setting, this
   returns its default value as a NUL-terminated string. Its bytes are
   static and immutable. Returns NULL if key is not a known
   configuration key.
*/
FSL_EXPORT char const * fsl_config_key_default_value(char const * key);

/**
   Returns true if f's current checkout contains the given
   versionable configuration setting, else false.

   @see fsl_config_ctrl
*/
FSL_EXPORT bool fsl_config_has_versionable( fsl_cx * f, char const * key );

static fsl_config_ctrl const fslConfigCtrl[] = {
/*
  These MUST stay sorted by name and the .defaultValue field MUST have
  a non-NULL value so that some API guarantees can be made.

  FIXME: bring this up to date wrt post-2014 fossil. Or abandon it
  altogether: it has since been decided that this library will not
  attempt to enforce application-level config constraints for the vast
  majority of fossil's config settings. The main benefit for us in
  keeping this is to be able to quickly look up versionable settings,
  as we really need to keep compatibility with fossil for those.
*/
  { "access-log",    0,                0, 0, "off"                 },
  { "allow-symlinks",0,                0, 0/*as of late 2020*/,
                                             "off"                 },
  { "auto-captcha",  "autocaptcha",    0, 0, "on"                  },
  { "auto-hyperlink",0,                0, 0, "on"                  },
  { "auto-shun",     0,                0, 0, "on"                  },
  { "autosync",      0,                0, 0, "on"                  },
  { "binary-glob",   0,               40, 1, ""                    },
  { "clearsign",     0,                0, 0, "off"                 },
#if defined(_WIN32) || defined(__CYGWIN__) || defined(__DARWIN__) || defined(__APPLE__)
  { "case-sensitive",0,                0, 0, "off"                 },
#else
  { "case-sensitive",0,                0, 0, "on"                  },
#endif
  { "clean-glob",    0,               40, 1, ""                    },
  { "crnl-glob",     0,               40, 1, ""                    },
  { "default-perms", 0,               16, 0, "u"                   },
  { "diff-binary",   0,                0, 0, "on"                  },
  { "diff-command",  0,               40, 0, ""                    },
  { "dont-push",     0,                0, 0, "off"                 },
  { "dotfiles",      0,                0, 1, "off"                 },
  { "editor",        0,               32, 0, ""                    },
  { "empty-dirs",    0,               40, 1, ""                    },
  { "encoding-glob",  0,              40, 1, ""                    },
  { "gdiff-command", 0,               40, 0, "gdiff"               },
  { "gmerge-command",0,               40, 0, ""                    },
  { "http-port",     0,               16, 0, "8080"                },
  { "https-login",   0,                0, 0, "off"                 },
  { "ignore-glob",   0,               40, 1, ""                    },
  { "keep-glob",     0,               40, 1, ""                    },
  { "localauth",     0,                0, 0, "off"                 },
  { "main-branch",   0,               40, 0, "trunk"               },
  { "manifest",      0,                0, 1, "off"                 },
  { "max-upload",    0,               25, 0, "250000"              },
  { "mtime-changes", 0,                0, 0, "on"                  },
  { "pgp-command",   0,               40, 0, "gpg --clearsign -o " },
  { "proxy",         0,               32, 0, "off"                 },
  { "relative-paths",0,                0, 0, "on"                  },
  { "repo-cksum",    0,                0, 0, "on"                  },
  { "self-register", 0,                0, 0, "off"                 },
  { "ssh-command",   0,               40, 0, ""                    },
  { "ssl-ca-location",0,              40, 0, ""                    },
  { "ssl-identity",  0,               40, 0, ""                    },
  { "tcl",           0,                0, 0, "off"                 },
  { "tcl-setup",     0,               40, 0, ""                    },
  { "th1-setup",     0,               40, 0, ""                    },
  { "web-browser",   0,               32, 0, ""                    },
  { "white-foreground", 0,             0, 0, "off"                 },
  { 0,0,0,0,0 }
};

char *fsl_db_setting_inop_rhs(){
  fsl_buffer x = fsl_buffer_empty;
  const char *zSep = "";
  fsl_config_ctrl const * ct = &fslConfigCtrl[0];
  int rc = fsl_buffer_append(&x, "(", 1);
  for( ; !rc && ct && ct->name; ++ct){
    rc = fsl_buffer_appendf(&x, "%s%Q", zSep, ct->name);
    zSep = ",";
  }
  if(!rc){
    rc = fsl_buffer_append(&x, ")", 1);
  }
  if(rc){
    fsl_buffer_clear(&x);
    assert(!x.mem);
  }else{
    fsl_buffer_resize(&x, x.used);
  }
  return (char *)x.mem;
}

static int fsl_config_ctrl_cmp(const void *lhs, const void *rhs){
  fsl_config_ctrl const * l = (fsl_config_ctrl const *)lhs;
  fsl_config_ctrl const * r = (fsl_config_ctrl const *)rhs;
  if(!l) return r ? -1 : 0;
  else if(!r) return 1;
  else return fsl_strcmp(l->name, r->name);
}

fsl_config_ctrl const * fsl_config_ctrl_get(char const * key){
  fsl_config_ctrl const * fcc;
  fsl_config_ctrl bogo = {0,0,0,0,0};
  bogo.name = key;
  fcc = (fsl_config_ctrl const *)
    bsearch( &bogo, fslConfigCtrl,
             ARRAYLEN(fslConfigCtrl) -1 /* for empty tail entry */,
             sizeof(fsl_config_ctrl),
             fsl_config_ctrl_cmp );
  return (fcc && fcc->name) ? fcc : NULL;
}

bool fsl_config_key_is_fossil(char const * key){
  fsl_config_ctrl const * fcc = fsl_config_ctrl_get(key);
  return (fcc && fcc->name) ? 1 : 0;
}

bool fsl_config_key_is_versionable(char const * key){
  fsl_config_ctrl const * fcc = fsl_config_ctrl_get(key);
  return (fcc && fcc->versionable) ? 1 : 0;
}

char const * fsl_config_key_default_value(char const * key){
  fsl_config_ctrl const * fcc = fsl_config_ctrl_get(key);
  return (fcc && fcc->name) ? fcc->defaultValue : NULL;
}

bool fsl_config_has_versionable( fsl_cx * f, char const * key ){
  if(!f || !key || !*key || !f->ckout.dir) return 0;
  else if(!fsl_config_key_is_fossil(key)) return 0;
  else{
    fsl_buffer * fn = fsl_cx_scratchpad(f);
    int rc = fsl_config_versionable_filename(f, key, fn);
    if(!rc) rc = fsl_stat(fsl_buffer_cstr(fn), NULL, 0);
    fsl_cx_scratchpad_yield(f, fn);
    return 0==rc;
  }
}


#undef SELECT_FROM_CONFIG
#undef MARKER
#undef ARRAYLEN
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/content.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 houses the code for the fsl_content_xxx() APIS.
*/
#include "fossil-scm/fossil-internal.h"
#include "fossil-scm/fossil-hash.h"
#include "fossil-scm/fossil-checkout.h"
#include <assert.h>
#include <memory.h> /* memcmp() */

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


fsl_int_t fsl_content_size( fsl_cx * f, fsl_id_t blobRid ){
  fsl_db * dbR = f ? fsl_cx_db_repo(f) : NULL;
  if(!f) return -3;
  else if(blobRid<=0) return -4;
  else if(!dbR) return -5;
  else{
    int rc;
    fsl_int_t rv = -2;
    fsl_stmt * q = NULL;
    rc = fsl_db_prepare_cached(dbR, &q,
                               "SELECT size FROM blob "
                               "WHERE rid=? "
                               "/*%s()*/",__func__);
    if(!rc){
      rc = fsl_stmt_bind_id(q, 1, blobRid);
      if(!rc){
        if(FSL_RC_STEP_ROW==fsl_stmt_step(q)){
          rv = (fsl_int_t)fsl_stmt_g_int64(q, 0);
        }
      }
      fsl_stmt_cached_yield(q);
    }
    return rv;
  }
}

bool fsl_content_is_available(fsl_cx * f, fsl_id_t rid){
  fsl_id_t srcid = 0;
  int rc = 0, depth = 0 /* Limit delta recursion depth */;
  while( depth++ < 100000 ){
    if( fsl_id_bag_contains(&f->cache.arty.missing, rid) ){
      return false;
    }else if( fsl_id_bag_contains(&f->cache.arty.available, rid) ){
      return true;
    }else if( fsl_content_size(f, rid)<0 ){
      fsl_id_bag_insert(&f->cache.arty.missing, rid)
        /* ignore possible OOM error */;
      return false;
    }
    rc = fsl_delta_src_id(f, rid, &srcid);
    if(rc) break;
    else if( 0==srcid ){
      fsl_id_bag_insert(&f->cache.arty.available, rid);
      return true;
    }
    rid = srcid;
  }
  if(0==rc){
    /* This "cannot happen" (never has historically, and would be
       indicative of what amounts to corruption in the repo). */
    fsl_fatal(FSL_RC_RANGE,"delta-loop in repository");
  }
  return false;
}



int fsl_content_blob( fsl_cx * f, fsl_id_t blobRid, fsl_buffer * tgt ){
  fsl_db * dbR = f ? fsl_cx_db_repo(f) : NULL;
  if(!f || !tgt) return FSL_RC_MISUSE;
  else if(blobRid<=0) return FSL_RC_RANGE;
  else if(!dbR) return FSL_RC_NOT_A_REPO;
  else{
    int rc;
    fsl_stmt * q = NULL;
    rc = fsl_db_prepare_cached( dbR, &q,
                                "SELECT content, size FROM blob "
                                "WHERE rid=?"
                                "/*%s()*/",__func__);
    if(!rc){
      rc = fsl_stmt_bind_id(q, 1, blobRid);
      if(!rc && (FSL_RC_STEP_ROW==(rc=fsl_stmt_step(q)))){
        void const * mem = NULL;
        fsl_size_t memLen = 0;
        if(fsl_stmt_g_int64(q, 1)<0){
          rc = fsl_cx_err_set(f, FSL_RC_PHANTOM,
                              "Cannot fetch content for phantom "
                              "blob #%"FSL_ID_T_PFMT".",
                              blobRid);
        }else{
          tgt->used = 0;
          fsl_stmt_get_blob(q, 0, &mem, &memLen);
          if(mem && memLen){
            rc = fsl_buffer_append(tgt, mem, memLen);
            if(!rc && fsl_buffer_is_compressed(tgt)){
              rc = fsl_buffer_uncompress(tgt, tgt);
            }
          }
        }
      }else if(FSL_RC_STEP_DONE==rc){
        rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                            "No blob found for rid %"FSL_ID_T_PFMT".",
                            blobRid);
      }
      fsl_stmt_cached_yield(q);
    }
    if(rc && !f->error.code && dbR->error.code){
      fsl_cx_uplift_db_error(f, dbR);
    }
    return rc;
  }
}


bool fsl_content_is_private(fsl_cx * f, fsl_id_t rid){
  fsl_stmt * s1 = NULL;
  fsl_db * db = fsl_cx_db_repo(f);
  int rc = db
    ? fsl_db_prepare_cached(db, &s1,
                            "SELECT 1 FROM private "
                            "WHERE rid=?"
                            "/*%s()*/",__func__)
    : FSL_RC_MISUSE;
  if(!rc){
    rc = fsl_stmt_bind_id(s1, 1, rid);
    if(!rc) rc = fsl_stmt_step(s1);
    fsl_stmt_cached_yield(s1);
  }
  return rc==FSL_RC_STEP_ROW ? true : false;
}


int fsl_content_get( fsl_cx * f, fsl_id_t rid, fsl_buffer * tgt ){
  fsl_db * db = fsl_cx_db_repo(f);
  if(!tgt) return FSL_RC_MISUSE;
  else if(rid<=0){
    return fsl_cx_err_set(f, FSL_RC_RANGE,
                          "RID %"FSL_ID_T_PFMT" is out of range.",
                          rid);
  }
  else if(!db){
    return fsl_cx_err_set(f, FSL_RC_NOT_A_REPO,
                          "Fossil has no repo opened.");
  }
  else{
    int rc;
    bool gotIt = 0;
    fsl_id_t nextRid;
    fsl_acache * const ac = &f->cache.arty;
    fsl_buffer_reuse(tgt);
    if(fsl_id_bag_contains(&ac->missing, rid)){
      /* Early out if we know the content is not available */
      return FSL_RC_NOT_FOUND;
    }

    /* Look for the artifact in the cache first */
    if( fsl_id_bag_contains(&ac->inCache, rid) ){
      fsl_size_t i;
      fsl_acache_line * line;
      for(i=0; i<ac->used; ++i){
        line = &ac->list[i];
        if( line->rid==rid ){
          rc = fsl_buffer_copy(&line->content, tgt);
          line->age = ac->nextAge++;
          return rc;
        }
      }
    }

    nextRid = 0;
    rc = fsl_delta_src_id(f, rid, &nextRid);
    /* MARKER(("rc=%d, nextRid=%"FSL_ID_T_PFMT"\n", rc, nextRid)); */
    if(rc) return rc;
    if( nextRid == 0 ){
      /* This is not a delta, so get its raw content. */
      rc = fsl_content_blob(f, rid, tgt);
      gotIt = 0==rc;
    }else{
      /* Looks like a delta, so let's expand it... */
      fsl_int_t n           /* number of used entries in 'a' */;
      fsl_int_t nAlloc = 10 /* number it items allocated in 'a' */;
      fsl_id_t * a = NULL    /* array of rids we expand */;
      fsl_int_t mx;
      fsl_buffer delta = fsl_buffer_empty;
      fsl_buffer next = fsl_buffer_empty  /* delta-applied content */ ;
      assert(nextRid>0);
      a = fsl_malloc( sizeof(a[0]) * nAlloc );
      if(!a) return FSL_RC_OOM;
      a[0] = rid;
      a[1] = nextRid;
      n = 1;
      while( !fsl_id_bag_contains(&ac->inCache, nextRid)
             && !fsl_delta_src_id(f, nextRid, &nextRid)
             && (nextRid>0)){
        /* Figure out how big n needs to be... */
        ++n;
        if( n >= nAlloc ){
          /* Expand 'a' */
          void * remem;
          if( n > fsl_db_g_int64(db, 0,
                                "SELECT max(rid) FROM blob")){
            rc = fsl_cx_err_set(f, FSL_RC_RANGE,
                                "Infinite loop in delta table.");
            goto end_delta;
          }
          nAlloc = nAlloc * 2;
          remem = fsl_realloc(a, nAlloc*sizeof(a[0]));
          if(!remem){
            rc = FSL_RC_OOM;
            goto end_delta;
          }
          a = (fsl_id_t*)remem;
        }
        a[n] = nextRid;
      }
      /**
         Recursively expand deltas to get the content...
      */
      mx = n;
      rc = fsl_content_get( f, a[n], tgt );
      /* MARKER(("Getting content for rid #%"FSL_ID_T_PFMT", rc=%d\n", a[n], rc)); */
      --n;
      for( ; !rc && (n>=0); --n){
        rc = fsl_content_blob(f, a[n], &delta);
        /* MARKER(("Getting/applying delta rid #%"FSL_ID_T_PFMT", rc=%d\n", a[n], rc)); */
        if(rc) goto end_delta;
        if(!delta.used){
          assert(!"Is this possible? The fossil tree has a similar "
                 "condition but i naively don't believe it's necessary.");
          continue;
        }
        next = fsl_buffer_empty;
        rc = fsl_buffer_delta_apply2(tgt, &delta, &next, &f->error);
        if(rc) goto end_delta;
#if 1
        /*
           In my (very simple) tests this cache costs us more than it
           saves. TODO: re-test this once we can do a 'rebuild', or
           something more intensive than processing a single
           manifest's R-card. At that point we can set a f->flags bit
           to enable or disable this block for per-use-case
           optimization purposes.

           We also probably want to cache fsl_deck instances instead
           of Manifest blobs (fsl_buffer) like fossil(1) does,
           otherwise this cache really doesn't save us much
           work/memory.

           2021-03-24: in a debug build, running:

           f-parseparty -t c -c -q

           (i.e.: parse and crosslink all checkin artifacts)

           on the libfossil repo with 2003 checkins takes:

           10.5s without this cache
           5.2s with this cache

           We shave another 0.5s if we always cache instead of using
           this mysterious (mx-n)%8 heuristic.
        */
        //MARKER(("mx=%d, n=%d, (mx-n)%%8=%d\n",
        //(int)mx, (int)n, (int)(mx-n)%8));
        //MARKER(("nAlloc=%d\n", (int)nAlloc));
        if( (mx-n)%8==0 ){
          //MARKER(("Caching artifact %d\n", (int)a[n+1]));
          rc = fsl_acache_insert( ac, a[n+1], tgt );
          if(rc){
            fsl_buffer_clear(&next);
            goto end_delta;
          }
          assert(!tgt->mem && "Passed to artifact cache.");
        }else{
          fsl_buffer_clear(tgt);
        }
#else
        if(mx){/*unused var*/}
        fsl_buffer_clear(tgt);
#endif
        *tgt = next;
      }
      end_delta:
      fsl_buffer_clear(&delta);
      fsl_free(a);
      gotIt = 0==rc;
    }

    if(!rc){
      rc = fsl_id_bag_insert(gotIt
                             ? &f->cache.arty.available
                             : &f->cache.arty.missing,
                             rid);
    }
    return rc;
  }
}

int fsl_content_get_sym( fsl_cx * f, char const * sym, fsl_buffer * tgt ){
  int rc;
  fsl_db * db = f ? fsl_needs_repo(f) : NULL;
  fsl_id_t rid = 0;
  if(!f || !sym || !tgt) return FSL_RC_MISUSE;
  else if(!db) return FSL_RC_NOT_A_REPO;
  rc = fsl_sym_to_rid(f, sym, FSL_SATYPE_ANY, &rid);
  return rc ? rc : fsl_content_get(f, rid, tgt);
}

/**
    Mark artifact rid as being available now. Update f's cache to show
    that everything that was formerly unavailable because rid was
    missing is now available. Returns 0 on success. f must have
    an opened repo and rid must be valid.
 */
static int fsl_content_mark_available(fsl_cx * f, fsl_id_t rid){
  fsl_id_bag pending = fsl_id_bag_empty;
  int rc;
  fsl_stmt * st = NULL;
  fsl_db * db = fsl_cx_db_repo(f);
  assert(f);
  assert(db);
  assert(rid>0);
  if( fsl_id_bag_contains(&f->cache.arty.available, rid) ) return 0;
  rc = fsl_id_bag_insert(&pending, rid);
  if(rc) goto end;
  while( (rid = fsl_id_bag_first(&pending))!=0 ){
    fsl_id_bag_remove(&pending, rid);
    rc = fsl_id_bag_insert(&f->cache.arty.available, rid);
    if(rc) goto end;
    fsl_id_bag_remove(&f->cache.arty.missing, rid);
    if(!st){
      rc = fsl_db_prepare_cached(db, &st,
                                 "SELECT rid FROM delta "
                                 "WHERE srcid=?"
                                 "/*%s()*/",__func__);
      if(rc) goto end;
    }
    rc = fsl_stmt_bind_id(st, 1, rid);
    while( !rc && (FSL_RC_STEP_ROW==fsl_stmt_step(st)) ){
      fsl_id_t const nx = fsl_stmt_g_id(st,0);
      assert(nx>0);
      rc = fsl_id_bag_insert(&pending, nx);
    }

  }
  end:
  if(st) fsl_stmt_cached_yield(st);
  fsl_id_bag_clear(&pending);
  return rc;
}

/**
   When a record is converted from a phantom to a real record, if that
   record has other records that are derived by delta, then call
   fsl_deck_crosslink() on those other records.

   If the formerly phantom record or any of the other records derived
   by delta from the former phantom are a baseline manifest, then also
   invoke fsl_deck_crosslink() on the delta-manifests associated with
   that baseline.

   Tail recursion is used to minimize stack depth.

   Returns 0 on success, any number of non-0 results on error.

   The 3rd argument must always be false except in recursive calls to
   this function.
*/
static int fsl_after_dephantomize(fsl_cx * f, fsl_id_t rid, bool doCrosslink){
  int rc = 0;
  unsigned nChildAlloc = 0;
  fsl_id_t * aChild = 0;
  fsl_buffer bufChild = fsl_buffer_empty;
  fsl_db * const db = fsl_cx_db_repo(f);
  fsl_stmt q = fsl_stmt_empty;

  MARKER(("WARNING: fsl_after_dephantomization() is UNTESTED.\n"));
  if(f->cache.ignoreDephantomizations) return 0;
  while(rid){
    unsigned nChildUsed = 0;
    unsigned i = 0;

    /* Parse the object rid itself */
    if(doCrosslink){
      fsl_deck deck = fsl_deck_empty;
      rc = fsl_deck_load_rid(f, &deck, rid, FSL_SATYPE_ANY);
      if(!rc){
        assert(aChild[i]==deck.rid);
        rc = fsl_deck_crosslink(&deck);
      }
      fsl_deck_finalize(&deck);
      if(rc) break;
    }
    /* Parse all delta-manifests that depend on baseline-manifest rid */
    rc = fsl_db_prepare(db, &q,
                        "SELECT rid FROM orphan WHERE baseline=%"FSL_ID_T_PFMT,
                        rid);
    if(rc) break;
    while(FSL_RC_STEP_ROW==fsl_stmt_step(&q)){
      fsl_id_t const child = fsl_stmt_g_id(&q, 0);
      if(nChildUsed>=nChildAlloc){
        nChildAlloc = nChildAlloc ? nChildAlloc*2 : 10;
        rc = fsl_buffer_reserve(&bufChild, sizeof(fsl_id_t)*nChildAlloc);
        if(rc) goto end;
        aChild = (fsl_id_t*)bufChild.mem;
      }
      aChild[nChildUsed++] = child;
    }
    fsl_stmt_finalize(&q);
    for(i=0; i<nChildUsed; ++i){
      fsl_deck deck = fsl_deck_empty;
      rc = fsl_deck_load_rid(f, &deck, aChild[i], FSL_SATYPE_ANY);
      if(!rc){
        assert(aChild[i]==deck.rid);
        rc = fsl_deck_crosslink(&deck);
      }
      fsl_deck_finalize(&deck);
      if(rc) goto end;
    }
    if( nChildUsed ){
      rc = fsl_db_exec_multi(db,
                             "DELETE FROM orphan WHERE baseline=%"FSL_ID_T_PFMT,
                             rid);
      if(rc){
        rc = fsl_cx_uplift_db_error(f, db);
      }
      break;
    }
    /* Recursively dephantomize all artifacts that are derived by
    ** delta from artifact rid and which have not already been
    ** cross-linked.  */
    nChildUsed = 0;
    rc = fsl_db_prepare(db, &q,
                        "SELECT rid FROM delta WHERE srcid=%"FSL_ID_T_PFMT
                        " AND NOT EXISTS(SELECT 1 FROM mlink WHERE mid=delta.rid)",
                        rid);
    if(rc){
      rc = fsl_cx_uplift_db_error(f, db);
      break;
    }
    while( FSL_RC_STEP_ROW==fsl_stmt_step(&q) ){
      fsl_id_t const child = fsl_stmt_g_id(&q, 0);
      if(nChildUsed>=nChildAlloc){
        nChildAlloc = nChildAlloc ? nChildAlloc*2 : 10;
        rc = fsl_buffer_reserve(&bufChild, sizeof(fsl_id_t)*nChildAlloc);
        if(rc) goto end;
        aChild = (fsl_id_t*)bufChild.mem;
      }
      aChild[nChildUsed++] = child;
    }
    fsl_stmt_finalize(&q);
    for(i=1; i<nChildUsed; ++i){
      rc = fsl_after_dephantomize(f, aChild[i], true);
      if(rc) break;
    }
    /* Tail recursion for the common case where only a single artifact
    ** is derived by delta from rid...
    ** (2021-06-06: this libfossil impl is not tail-recursive due to
    ** necessary cleanup) */
    rid = nChildUsed>0 ? aChild[0] : 0;
    doCrosslink = true;
  }
  end:
  fsl_stmt_finalize(&q);
  fsl_buffer_clear(&bufChild);
  return rc;
}

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){
  fsl_size_t size;
  fsl_id_t rid;
  fsl_stmt * s1 = NULL;
  fsl_buffer cmpr = fsl_buffer_empty;
  fsl_buffer hash = fsl_buffer_empty;
  bool markAsUnclustered = false;
  bool markAsUnsent = true;
  bool isDephantomize = false;
  fsl_db * dbR = fsl_cx_db_repo(f);
  int const zUuidLen = zUuid ? fsl_is_uuid(zUuid) : 0;
  int rc = 0;
  bool inTrans = false;
  assert(f);
  assert(dbR);
  assert(pBlob);
  assert(srcId==0 || zUuid!=NULL);
  assert(!zUuid || zUuidLen);
  if(!dbR) return FSL_RC_NOT_A_REPO;
  static const fsl_size_t MaxSize = 0x70000000;
  if(pBlob->used>=MaxSize || uncompSize>=MaxSize){
    /* fossil(1) uses int for all blob sizes, and therefore has a
       hard-coded limit of 2GB max size per blob. That property of the
       API is well-entrenched, and correcting it properly, including
       all algorithms which access blobs using integer indexes, would
       require a large coding effort with a non-trivial risk of
       lingering, difficult-to-trace bugs.

       For compatibility, we limit ourselves to 2GB, but to ensure a
       bit of leeway, we set our limit slightly less than 2GB.
    */
    return fsl_cx_err_set(f, FSL_RC_RANGE,
                          "For compatibility with fossil(1), "
                          "blobs may not exceed %d bytes in size.",
                          (int)MaxSize);
  }
  if(!zUuid){
    assert(0==uncompSize);
    /* "auxiliary hash" bits from:
       https://fossil-scm.org/fossil/file?ci=c965636958eb58aa&name=src%2Fcontent.c&ln=527-537
    */
    /* First check the auxiliary hash to see if there is already an artifact
    ** that uses the auxiliary hash name */
    /* 2021-04-13: we can now use fsl_repo_blob_lookup() to do this,
       but the following code is known to work, so touching it is a
       low priority. */
    rc = fsl_cx_hash_buffer(f, true, pBlob, &hash);
    if(FSL_RC_UNSUPPORTED==rc) rc = 0;
    else if(rc) goto end;
    assert(hash.used==0 || hash.used>=FSL_STRLEN_SHA1);
    rid = hash.used ? fsl_uuid_to_rid(f, fsl_buffer_cstr(&hash)) : 0;
    assert(rid>=0 && "Cannot have malformed/ambiguous UUID at this point.");
    if(!rid){
      /* No existing artifact with the auxiliary hash name.  Therefore, use
      ** the primary hash name. */
      hash.used = 0;
      rc = fsl_cx_hash_buffer(f, false, pBlob, &hash);
      if(rc) goto end;
      assert(hash.used>=FSL_STRLEN_SHA1);
    }
  }else{
    rc = fsl_buffer_append(&hash, zUuid, zUuidLen);
    if(rc) goto end;
  }
  assert(!rc);
  if(uncompSize){
    /* pBlob is assumed to be compressed. */
    assert(fsl_buffer_is_compressed(pBlob));
    size = uncompSize;
  }else{
    size = pBlob->used;
    if(srcId>0){
      rc = fsl_delta_applied_size(pBlob->mem, pBlob->used, &size);
      if(rc) goto end;
    }
  }
  rc = fsl_db_transaction_begin(dbR);
  if(rc) goto end;
  inTrans = true;
  if( f->cxConfig.hashPolicy==FSL_HPOLICY_AUTO && hash.used>FSL_STRLEN_SHA1 ){
    fsl_cx_err_reset(f);
    fsl_cx_hash_policy_set(f, FSL_HPOLICY_SHA3);
    if((rc = f->error.code)){
      goto end;
    }
  }
  /* Check to see if the entry already exists and if it does whether
     or not the entry is a phantom. */
  rc = fsl_db_prepare_cached(dbR, &s1,
                             "SELECT rid, size FROM blob "
                             "WHERE uuid=?"
                             "/*%s()*/",__func__);
  if(rc) goto end;
  rc = fsl_stmt_bind_step( s1, "b", &hash);
  switch(rc){
    case FSL_RC_STEP_ROW:
      rc = 0;
      rid = fsl_stmt_g_id(s1, 0);
      if( fsl_stmt_g_int64(s1, 1)>=0 ){
        /* The entry is not a phantom. There is nothing for us to do
           other than return the RID.
        */
        /*
          Reminder: the do-nothing-for-empty-phantom behaviour is
          arguable (but historical). There is a corner case there
          involving an empty file. So far, so good, though. After
          all...  all empty files have the same hash.
        */
        fsl_stmt_cached_yield(s1);
        assert(inTrans);
        fsl_db_transaction_end(dbR,0);
        if(outRid) *outRid = rid;
        fsl_buffer_clear(&hash);
        return 0;
      }
      break;
    case 0:
      /* No entry with the same UUID currently exists */
      rid = 0;
      markAsUnclustered = true;
      break;
    default:
      goto end;
  }
  if(s1){
    fsl_stmt_cached_yield(s1);
    s1 = NULL;
  }
  if(rc) goto end;

#if 0
  /* Requires app-level data. We might need a client hook mechanism or
     other metadata here.
  */
  /* Construct a received-from ID if we do not already have one */
  if( f->cache.rcvid <= 0 ){
    /* FIXME: use cached statement. */
    rc = fsl_db_exec(dbR, 
       "INSERT INTO rcvfrom(uid, mtime, nonce, ipaddr)"
       "VALUES(%d, julianday('now'), %Q, %Q)",
       g.userUid, g.zNonce, g.zIpAddr
    );
    f->cache.rcvid = fsl_db_last_insert_id(dbR);
  }
#endif

  if( uncompSize ){
    cmpr = *pBlob;
  }else{
    rc = fsl_buffer_compress(pBlob, &cmpr);
    if(rc) goto end;
  }

  if( rid>0 ){
#if 0
    assert(!"NYI: adding data to phantom. Requires some missing pieces.");
    rc = fsl_cx_err_set(f, FSL_RC_NYI,
                        "NYI: adding data to phantom. "
                        "Requires missing rcvId pieces.");
    goto end;
#else
    /* We are just adding data to a phantom */
    rc = fsl_db_prepare_cached(dbR, &s1,
                               "UPDATE blob SET "
                               "rcvid=?, size=?, content=? "
                               "WHERE rid=?"
                               "/*%s()*/",__func__);
    if(rc) goto end;
    rc = fsl_stmt_bind_step(s1, "RIBR", f->cache.rcvId, (int64_t)size,
                            &cmpr, rid);
    if(!rc){
      rc = fsl_db_exec(dbR, "DELETE FROM phantom "
                       "WHERE rid=%"FSL_ID_T_PFMT, rid
                       /* FIXME? use cached statement? */);
      if( !rc && (srcId==0 ||
                  0==fsl_acache_check_available(f, srcId)) ){
        isDephantomize = true;
        rc = fsl_content_mark_available(f, rid);
      }
    }
    fsl_stmt_cached_yield(s1);
    s1 = NULL;
    if(rc) goto end;
#endif
  }else{
    /* We are creating a new entry */
    rc = fsl_db_prepare_cached(dbR, &s1,
                               "INSERT INTO blob "
                               "(rcvid,size,uuid,content) "
                               "VALUES(?,?,?,?)"
                               "/*%s()*/",__func__);
    if(rc) goto end;
    rc = fsl_stmt_bind_step(s1, "RIbB", f->cache.rcvId, (int64_t)size,
                            &hash, &cmpr);
    if(!rc){
      rid = fsl_db_last_insert_id(dbR);
      if(!pBlob ){
        rc = fsl_db_exec_multi(dbR,/* FIXME? use cached statement? */
                               "INSERT OR IGNORE INTO phantom "
                               "VALUES(%"FSL_ID_T_PFMT")",
                               rid);
        markAsUnsent = false;
      }
      if( !rc && (f->cache.markPrivate || isPrivate) ){
        rc = fsl_db_exec_multi(dbR,/* FIXME? use cached statement? */
                               "INSERT INTO private "
                               "VALUES(%"FSL_ID_T_PFMT")",
                               rid);
        markAsUnclustered = false;
        markAsUnsent = false;
      }
    }
    if(rc) rc = fsl_cx_uplift_db_error2(f, dbR, rc);
    fsl_stmt_cached_yield(s1);
    s1 = NULL;
    if(rc) goto end;
  }

  /* If the srcId is specified, then the data we just added is
     really a delta. Record this fact in the delta table.
  */
  if( srcId ){
    rc = fsl_db_prepare_cached(dbR, &s1,
                               "REPLACE INTO delta(rid,srcid) "
                               "VALUES(?,?)"
                               "/*%s()*/",__func__);
    if(!rc){
      rc = fsl_stmt_bind_step(s1, "RR", rid, srcId);
      if(rc) rc = fsl_cx_uplift_db_error2(f, dbR, rc);
      fsl_stmt_cached_yield(s1);
      s1 = NULL;
    }
    if(rc) goto end;
  }
  if( !isDephantomize
      && fsl_id_bag_contains(&f->cache.arty.missing, rid) && 
      (srcId==0 || (0==fsl_acache_check_available(f,srcId)))){
    /*
      TODO: document what this is for.
      TODO: figure out what that is.
    */
    rc = fsl_content_mark_available(f, rid);
    if(rc) goto end;
  }
  if( isDephantomize ){
    rc = fsl_after_dephantomize(f, rid, false);
    if(rc) goto end;
  }

  /* Add the element to the unclustered table if has never been
     previously seen.
  */
  if( markAsUnclustered ){
    /* FIXME: use a cached statement. */
    rc = fsl_db_exec_multi(dbR,
                           "INSERT OR IGNORE INTO unclustered VALUES"
                           "(%"FSL_ID_T_PFMT")", rid);
    if(rc) goto end;
  }

  if( markAsUnsent ){
    /* FIXME: use a cached statement. */
    rc = fsl_db_exec(dbR, "INSERT OR IGNORE INTO unsent "
                     "VALUES(%"FSL_ID_T_PFMT")", rid);
    if(rc) goto end;
  }
  
  rc = fsl_repo_verify_before_commit(f, rid);
  if(rc) goto end /* FSL_RC_OOM is basically the "only possible" failure
                     after this point. */;
  /* Code after end: relies on the following 2 lines: */
  rc = fsl_db_transaction_end(dbR, false);
  inTrans = false;
  if(!rc){
    if(outRid) *outRid = rid;
  }
  end:
  if(inTrans){
    assert(0!=rc);
    fsl_db_transaction_end(dbR,true);
  }
  fsl_buffer_clear(&hash);
  if(!uncompSize){
    fsl_buffer_clear(&cmpr);
  }/* else cmpr.mem (if any) belongs to pBlob */
  return rc;
}

int fsl_content_put( fsl_cx * f, fsl_buffer const * pBlob, fsl_id_t * newRid){
  return fsl_content_put_ex(f, pBlob, NULL, 0, 0, 0, newRid);
}

int fsl_uuid_is_shunned(fsl_cx * f, fsl_uuid_cstr zUuid){
  fsl_db * db = fsl_cx_db_repo(f);
  if( !db || zUuid==0 || zUuid[0]==0 ) return 0;
  else if(FSL_HPOLICY_SHUN_SHA1==f->cxConfig.hashPolicy
          && FSL_STRLEN_SHA1==fsl_is_uuid(zUuid)){
    return 1;
  }
  /* TODO? cached query */
  return 1==fsl_db_g_int32( db, 0,
                            "SELECT 1 FROM shun WHERE uuid=%Q",
                            zUuid);
}

int fsl_content_new( fsl_cx * f, fsl_uuid_cstr uuid, bool isPrivate,
                     fsl_id_t * newId ){
  fsl_id_t rid = 0;
  int rc;
  fsl_db * db = fsl_cx_db_repo(f);
  fsl_stmt * s1 = NULL, * s2 = NULL;
  int const uuidLen = uuid ? fsl_is_uuid(uuid) : 0;
  if(!f || !uuid) return FSL_RC_MISUSE;
  else if(!uuidLen) return FSL_RC_RANGE;
  if(!db) return FSL_RC_NOT_A_REPO;
  if( fsl_uuid_is_shunned(f, uuid) ){
    return fsl_cx_err_set(f, FSL_RC_ACCESS,
                          "UUID is shunned: %s", uuid)
      /* need new error code? */;
  }
  rc = fsl_db_transaction_begin(db);
  if(rc) return rc;

  rc = fsl_db_prepare_cached(db, &s1,
                             "INSERT INTO blob(rcvid,size,uuid,content)"
                             "VALUES(0,-1,?,NULL)"
                             "/*%s()*/",__func__);
  if(rc) goto end;
  rc = fsl_stmt_bind_text(s1, 1, uuid, uuidLen, 0);
  if(!rc) rc = fsl_stmt_step(s1);
  fsl_stmt_cached_yield(s1);
  if(FSL_RC_STEP_DONE!=rc) goto end;
  else rc = 0;
  rid = fsl_db_last_insert_id(db);
  assert(rid>0);
  rc = fsl_db_prepare_cached(db, &s2,
                             "INSERT INTO phantom VALUES (?)"
                             "/*%s()*/",__func__);
  if(rc) goto end;
  rc = fsl_stmt_bind_id(s2, 1, rid);
  if(!rc) rc = fsl_stmt_step(s2);
  fsl_stmt_cached_yield(s2);
  if(FSL_RC_STEP_DONE!=rc) goto end;
  else rc = 0;

  if( f->cache.markPrivate || isPrivate ){
    /* Should be seldom enough that we don't need to cache
       this statement. */
    rc = fsl_db_exec(db,
                     "INSERT INTO private VALUES(%"FSL_ID_T_PFMT")",
                     (fsl_id_t)rid);
  }else{
    fsl_stmt * s3 = NULL;
    rc = fsl_db_prepare_cached(db, &s3,
                               "INSERT INTO unclustered VALUES(?)");
    if(!rc){
      rc = fsl_stmt_bind_id(s3, 1, rid);
      if(!rc) rc = fsl_stmt_step(s3);
      fsl_stmt_cached_yield(s3);
      if(FSL_RC_STEP_DONE!=rc) goto end;
      else rc = 0;
    }
  }

  if(!rc) rc = fsl_id_bag_insert(&f->cache.arty.missing, rid);
  
  end:
  if(rc){
    if(db->error.code && !f->error.code){
      fsl_cx_uplift_db_error(f, db);
    }
    fsl_db_transaction_rollback(db);
  }
  else{
    rc = fsl_db_transaction_commit(db);
    if(!rc && newId) *newId = rid;
    else if(rc && !f->error.code){
      fsl_cx_uplift_db_error(f, db);
    }
  }
  return rc;
}

int fsl_content_undeltify(fsl_cx * f, fsl_id_t rid){
  int rc;
  fsl_db * db = f ? fsl_cx_db_repo(f) : NULL;
  fsl_id_t srcid = 0;
  fsl_buffer x = fsl_buffer_empty;
  fsl_stmt s = fsl_stmt_empty;
  if(!f) return FSL_RC_MISUSE;
  else if(!db) return FSL_RC_NOT_A_REPO;
  else if(rid<=0) return FSL_RC_RANGE;
  rc = fsl_db_transaction_begin(db);
  if(rc) return fsl_cx_uplift_db_error2(f, db, rc);
  /* Reminder: the original impl does not do this in a
     transaction, _possibly_ because it's only done from places
     where a transaction is active (that's unconfirmed).
     Nested transactions are very cheap, though.
  */
  rc = fsl_delta_src_id( f, rid, &srcid );
  if(rc || srcid<=0) goto end;
  rc = fsl_content_get(f, rid, &x);
  if( rc || !x.used ) goto end;
  /* TODO? use cached statements */
  rc = fsl_db_prepare(db, &s,
                      "UPDATE blob SET content=?,"
                      " size=%" FSL_SIZE_T_PFMT
                      " WHERE rid=%" FSL_ID_T_PFMT,
                      x.used, rid);
  if(rc) goto dberr;
  rc = fsl_buffer_compress(&x, &x);
  if(rc) goto end;
  rc = fsl_stmt_bind_blob(&s, 1, x.mem,
                          (fsl_int_t)x.used, 0);
  if(rc) goto dberr;
  rc = fsl_stmt_step(&s);
  if(FSL_RC_STEP_DONE==rc) rc = 0;
  else goto dberr;
  rc = fsl_db_exec(db, "DELETE FROM delta "
                   "WHERE rid=%"FSL_ID_T_PFMT,
                   (fsl_id_t)rid);
  if(rc) goto dberr;
#if 0
  /*
    fossil does not do this, but that seems like an inconsistency.

    On that topic Richard says:

    "When you undelta an artifact, however, it is then stored as
    plain text.  (Actually, as zlib compressed plain text.)  There
    is no possibility of delta loops or bugs in the delta encoder or
    missing source artifacts.  And so there is much less of a chance
    of losing content.  Hence, I didn't see the need to verify the
    content of artifacts that are undelta-ed."

    Potential TODO: f->flags FSL_CX_F_PEDANTIC_VERIFICATION, which
    enables the R-card and this check, and any similarly superfluous
    ones.
  */
  if(!rc) fsl_repo_verify_before_commit(f, rid);
#endif
  end:
  fsl_buffer_clear(&x);
  fsl_stmt_finalize(&s);
  if(rc) fsl_db_transaction_rollback(db);
  else rc = fsl_db_transaction_commit(db);
  return rc;
  dberr:
  assert(rc);
  rc = fsl_cx_uplift_db_error2(f, db, rc);
  goto end;
}

int fsl_content_deltify(fsl_cx * f, fsl_id_t rid,
                        fsl_id_t srcid, bool force){
  fsl_id_t s;
  fsl_buffer data = fsl_buffer_empty;
  fsl_buffer src = fsl_buffer_empty;
  fsl_buffer delta = fsl_buffer_empty;
  fsl_db * db = f ? fsl_cx_db_repo(f) : NULL;
  int rc = 0;
  enum { MinSizeThreshold = 50 };
  if(!f) return FSL_RC_MISUSE;
  else if(rid<=0 || srcid<=0) return FSL_RC_RANGE;
  else if(!db) return FSL_RC_NOT_A_REPO;
  else if( srcid==rid ) return 0;
  else if(!fsl_content_is_available(f, rid)){
    return 0;
  }
  if(!force){
    fsl_id_t tmpRid = 0;
    rc = fsl_delta_src_id(f, rid, &tmpRid);
    if(tmpRid>0){
      /*
        We already have a delta, it seems. Nothing left to do
        :-D. Should we return FSL_RC_ALREADY_EXISTS here?
      */
      return 0;
    }
    else if(rc) return rc;
  }

  if( fsl_content_is_private(f, srcid)
      && !fsl_content_is_private(f, rid) ){
    /*
      See API doc comments about crossing the private/public
      boundaries. Do we want to report okay here or
      FSL_RC_ACCESS? Not yet sure how this routine is used.

      Since delitifying is an internal optimization/implementation
      detail, it seems best to return 0 for this case.
    */
    return 0;
  }
  /**
     Undeltify srcid if needed...
  */
  s = srcid;
  while( (0==(rc=fsl_delta_src_id(f, s, &s)))
         && (s>0) ){
    if( s==rid ){
      rc = fsl_content_undeltify(f, srcid);
      break;
    }
  }
  if(rc) return rc;
  /* As of here, don't return on error. Use (goto end) instead, or be
     really careful, b/c buffers might need cleaning. */
  rc = fsl_content_get(f, srcid, &src);
  if(rc
     || (src.used < MinSizeThreshold)
     /* See API doc comments about minimum size to delta/undelta. */
     ) goto end;
  rc = fsl_content_get(f, rid, &data);
  if(rc || (data.used < MinSizeThreshold)) goto end;
  rc = fsl_buffer_delta_create(&src, &data, &delta);
  if( !rc && (delta.used <= (data.used * 3 / 4 /* 75% */))){
    fsl_stmt * s1 = NULL;
    fsl_stmt * s2 = NULL;
    rc = fsl_buffer_compress(&delta, &delta);
    if(rc) goto end;
    rc = fsl_db_prepare_cached(db, &s1,
                               "UPDATE blob SET content=? "
                               "WHERE rid=?/*%s()*/",__func__);
    if(!rc){
      fsl_stmt_bind_id(s1, 2, rid);
      rc = fsl_stmt_bind_blob(s1, 1, delta.mem, delta.used, 0);
      if(!rc){
        rc = fsl_db_prepare_cached(db, &s2,
                                   "REPLACE INTO delta(rid,srcid) "
                                   "VALUES(?,?)/*%s()*/",__func__);
        if(!rc){
          fsl_stmt_bind_id(s2, 1, rid);
          fsl_stmt_bind_id(s2, 2, srcid);
          rc = fsl_db_transaction_begin(db);
          if(!rc){
            rc = fsl_stmt_step(s1);
            if(FSL_RC_STEP_DONE==rc){
              rc = fsl_stmt_step(s2);
              if(FSL_RC_STEP_DONE==rc) rc = 0;
            }
            if(!rc) rc = fsl_db_transaction_end(db, 0);
            else fsl_db_transaction_end(db, 1) /* keep rc intact */;
          }
        }
      }
    }
    fsl_stmt_cached_yield(s1);
    fsl_stmt_cached_yield(s2);
    if(!rc) fsl_repo_verify_before_commit(f, rid);
  }
  end:
  if(rc && db->error.code && !f->error.code){
    fsl_cx_uplift_db_error(f,db);
  }
  fsl_buffer_clear(&src);
  fsl_buffer_clear(&data);
  fsl_buffer_clear(&delta);
  return rc;
}

/**
    Removes all entries from the repo's blob table which are listed in
    the shun table.
 */
int fsl_repo_shun_artifacts(fsl_cx * f){
  fsl_stmt q = fsl_stmt_empty;
  int rc;
  fsl_db * db = f ? fsl_cx_db_repo(f) : NULL;
  if(!f) return FSL_RC_MISUSE;
  else if(!db) return FSL_RC_NOT_A_REPO;
  rc = fsl_db_transaction_begin(db);
  if(rc) return rc;
  rc = fsl_db_exec_multi(db,
                         "CREATE TEMP TABLE IF NOT EXISTS "
                         "toshun(rid INTEGER PRIMARY KEY);"
                         "INSERT INTO toshun SELECT rid FROM blob, shun "
                         "WHERE blob.uuid=shun.uuid;"
  );
  if(rc) goto end;
  /* Ensure that deltas generated from the to-be-shunned data
     are unpacked into non-delta form...
  */
  rc = fsl_db_prepare(db, &q,
                      "SELECT rid FROM delta WHERE srcid IN toshun"
                      );
  if(rc) goto end;
  while( !rc && (FSL_RC_STEP_ROW==fsl_stmt_step(&q)) ){
    fsl_id_t const srcid = fsl_stmt_g_id(&q, 0);
    rc = fsl_content_undeltify(f, srcid);
  }
  fsl_stmt_finalize(&q);
  if(!rc){
    rc = fsl_db_exec_multi(db,
            "DELETE FROM delta WHERE rid IN toshun;"
            "DELETE FROM blob WHERE rid IN toshun;"
            "DROP TABLE toshun;"
            "DELETE FROM private "
            "WHERE NOT EXISTS "
            "(SELECT 1 FROM blob WHERE rid=private.rid);"
    );
  }
  end:
  if(!rc) rc = fsl_db_transaction_commit(db);
  else fsl_db_transaction_rollback(db);
  if(rc && db->error.code && !f->error.code){
    rc = fsl_cx_uplift_db_error(f, db);
  }
  return rc;
}

int fsl_content_make_public(fsl_cx * f, fsl_id_t rid){
  int rc;
  fsl_db * db = f ? fsl_cx_db_repo(f) : NULL;
  if(!f) return FSL_RC_MISUSE;
  else if(!db) return FSL_RC_NOT_A_REPO;
  rc = fsl_db_exec(db, "DELETE FROM private "
                   "WHERE rid=%" FSL_ID_T_PFMT, rid);
  return rc ? fsl_cx_uplift_db_error(f, db) : 0;
}

/**
    Load the record ID rid and up to N-1 closest ancestors into
    the "fsl_computed_ancestors" table.
 */
static int fsl_compute_ancestors( fsl_db * db, fsl_id_t rid,
                                  int N, char directOnly ){
  fsl_stmt st = fsl_stmt_empty;
  int rc = fsl_db_prepare(db, &st,
    "WITH RECURSIVE "
    "  ancestor(rid, mtime) AS ("
    "    SELECT ?, mtime "
    "      FROM event WHERE objid=? "
    "    UNION "
    "    SELECT plink.pid, event.mtime"
    "      FROM ancestor, plink, event"
    "     WHERE plink.cid=ancestor.rid"
    "       AND event.objid=plink.pid %s"
    "     ORDER BY mtime DESC LIMIT ?"
    "  )"
    "INSERT INTO fsl_computed_ancestors"
    "  SELECT rid FROM ancestor;",
    directOnly ? "AND plink.isPrim" : ""
  );
  if(!rc){
    fsl_stmt_bind_id(&st, 1, rid);
    fsl_stmt_bind_id(&st, 2, rid);
    fsl_stmt_bind_int32(&st, 3, (int32_t)N);
    rc = fsl_stmt_step(&st);
    if(FSL_RC_STEP_DONE==rc){
      rc = 0;
    }
  }
  fsl_stmt_finalize(&st);
  return rc;
}

int fsl_mtime_of_F_card(fsl_cx * f, fsl_id_t vid, fsl_card_F const * fc, fsl_time_t *pMTime){
  if(!f || !fc) return FSL_RC_MISUSE;
  else if(vid<=0) return FSL_RC_RANGE;
  else if(!fc->uuid){
    if(pMTime) *pMTime = 0;
    return 0;
  }else{
    fsl_id_t fid = fsl_uuid_to_rid(f, fc->uuid);
    if(fid<=0){
      assert(f->error.code);
      return f->error.code;
    }else{
      return fsl_mtime_of_manifest_file(f, vid, fid, pMTime);
    }
  }
}

int fsl_mtime_of_manifest_file(fsl_cx * f, fsl_id_t vid, fsl_id_t fid, fsl_time_t *pMTime){
  fsl_db * db = fsl_needs_repo(f);
  fsl_stmt * q = NULL;
  int rc;
  if(!db) return FSL_RC_NOT_A_REPO;

  if(fid<=0){
    /* Only fetch the checkin time... */
    int64_t i = -1;
    rc = fsl_db_get_int64(db, &i, 
                          "SELECT (mtime-2440587.5)*86400 "
                          "FROM event WHERE objid=%"FSL_ID_T_PFMT
                          " AND type='ci'",
                          (fsl_id_t)vid);
    if(!rc){
      if(i<0) rc = FSL_RC_NOT_FOUND;
      else if(pMTime) *pMTime = (fsl_time_t)i;
    }
    return rc;
  }

  if( f->cache.mtimeManifest != vid ){
    /*
      Computing (and keeping) ancestors is relatively costly, so we
      keep only the copy associated with f->cache.mtimeManifest
      around. For the general case, we will be feeding this function
      files from the same manifest.
    */
    f->cache.mtimeManifest = vid;
    rc = fsl_db_exec_multi(db,"DROP TABLE IF EXISTS temp.fsl_computed_ancestors;"
                           "CREATE TEMP TABLE fsl_computed_ancestors"
                           "(x INTEGER PRIMARY KEY);");
    if(!rc){
      rc = fsl_compute_ancestors(db, vid, 1000000, 1);
    }
    if(rc){
      fsl_cx_uplift_db_error(f, db);
      return rc;
    }
  }
  rc = fsl_db_prepare_cached(db, &q,
    "SELECT (max(event.mtime)-2440587.5)*86400 FROM mlink, event"
    " WHERE mlink.mid=event.objid"
    "   AND mlink.fid=?"
    "   AND +mlink.mid IN fsl_computed_ancestors"
  );
  if(!rc){
    fsl_stmt_bind_id(q, 1, fid);
    rc = fsl_stmt_step(q);
    if( FSL_RC_STEP_ROW==rc ){
      rc = 0;
      if(pMTime) *pMTime = (fsl_time_t)fsl_stmt_g_int64(q, 0);
    }else{
      assert(rc);
      if(FSL_RC_STEP_DONE==rc) rc = FSL_RC_NOT_FOUND;
    }
    fsl_stmt_cached_yield(q);
  }
  return rc;
}

int fsl_card_F_content( fsl_cx * f, fsl_card_F const * fc,
                        fsl_buffer * dest ){
  if(!f || !fc || !dest) return FSL_RC_MISUSE;
  else if(!fc->uuid){
    return fsl_cx_err_set(f, FSL_RC_RANGE,
                          "Cannot fetch content of a deleted file "
                          "because it has no UUID.");
  }
  else if(!fsl_needs_repo(f)) return FSL_RC_NOT_A_REPO;
  else{
    fsl_id_t const rid = fsl_uuid_to_rid(f, fc->uuid);
    if(!rid) return fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                                   "UUID not found: %s",
                                   fc->uuid);
    else if(rid<0){
      assert(f->error.code);
      return f->error.code;
    }else{
      return fsl_content_get(f, rid, dest);
    }
  }
}


/**
   UNTESTED (but closely derived from known-working code).

   Expects f to have an opened checkout. Assumes zName is resolvable
   (via fsl_ckout_filename_check() - see that function for the
   meaning of the relativeToCwd argument) to a path under the current
   checkout root. It loads the file's contents and stores them into
   the blob table. If rid is not NULL, *rid is assigned the blob.rid
   (possibly new, possilbly re-used!). If uuid is not NULL then *uuid
   is assigned to the content's UUID. The *uuid bytes are owned by the
   caller, who must eventually fsl_free() them. If content with the
   same UUID already exists, it does not get re-imported but rid/uuid
   will (if not NULL) contain the values of any previous content
   with the same hash.

   ACHTUNG: this function DOES NOT CARE whether or not the file is
   actually part of a checkout or not, nor whether it is actually
   referenced by any checkins, or such, other than that it must
   resolve to something under the checkout root (to avoid breaking any
   internal assumptions in fossil about filenames). It will add new
   repo.filename entries as needed for this function. Thus is can be
   used to import "shadow files" either not known about by fossil or
   not _yet_ known about by fossil.

   If parentRid is >0 then it must refer to the previous version of
   zName's content. The parent version gets deltified vs the new one,
   but deltification is a suggestion which the library will ignore if
   (e.g.) the parent content is already a delta of something else.

   This function does its DB-side work in a transaction, so, e.g.  if
   saving succeeds but deltification of the parent version fails for
   some reason, the whole save operation is rolled back.

   Returns 0 on success. On error rid and uuid are not modified.
*/
int fsl_import_file( fsl_cx * f, char relativeToCwd,
                     char const * zName,
                     fsl_id_t parentRid,
                     fsl_id_t *rid, fsl_uuid_str * uuid ){
  fsl_buffer * canon = 0; // canonicalized filename
  fsl_buffer * nbuf = 0; // filename buffer
  fsl_buffer * fbuf = &f->fileContent; // file content buffer
  char const * fn;
  int rc;
  fsl_id_t fnid = 0;
  fsl_id_t rcRid = 0;
  fsl_db * db = f ? fsl_needs_repo(f) : NULL;
  char inTrans = 0;
  if(!zName || !*zName) return FSL_RC_MISUSE;
  else if(!f->ckout.dir) return FSL_RC_NOT_A_CKOUT;
  else if(!db) return FSL_RC_NOT_A_REPO;
  canon = fsl_cx_scratchpad(f);
  nbuf = fsl_cx_scratchpad(f);

  assert(!fbuf->used && "Misuse of f->fileContent");
  assert(f->ckout.dir);

  /* Normalize the name... i often regret having
     fsl_ckout_filename_check() return checkout-relative paths.
  */
  rc = fsl_ckout_filename_check(f, relativeToCwd, zName, canon);
  if(rc) goto end;

  /* Find or create a repo.filename entry... */
  fn = fsl_buffer_cstr(canon);

  rc = fsl_db_transaction_begin(db);
  if(rc) goto end;
  inTrans = 1;

  rc = fsl_repo_filename_fnid2(f, fn, &fnid, 1);
  if(rc) goto end;

  /* Import the file... */
  assert(fnid>0);
  rc = fsl_buffer_appendf(nbuf, "%s%s", f->ckout.dir, fn);
  if(rc) goto end;
  fn = fsl_buffer_cstr(nbuf);
  rc = fsl_buffer_fill_from_filename( fbuf, fn );
  if(rc){
    fsl_cx_err_set(f, rc, "Error %s importing file: %s",
                   fsl_rc_cstr(rc), fn);
    goto end;
  }
  fn = NULL;
  rc = fsl_content_put( f, fbuf, &rcRid );
  if(!rc){
    assert(rcRid > 0);
    if(parentRid>0){
      /* Make parent version a delta of this one, if possible... */
      rc = fsl_content_deltify(f, parentRid, rcRid, 0);
    }
    if(!rc){
      if(rid) *rid = rcRid;
      if(uuid){
        fsl_cx_err_reset(f);
        *uuid = fsl_rid_to_uuid(f, rcRid);
        if(!*uuid) rc = (f->error.code ? f->error.code : FSL_RC_OOM);
      }
    }
  }

  if(!rc){
    assert(inTrans);
    inTrans = 0;
    rc = fsl_db_transaction_commit(db);
  }

  end:
  fsl_cx_content_buffer_yield(f);
  assert(0==fbuf->used);
  fsl_cx_scratchpad_yield(f, canon);
  fsl_cx_scratchpad_yield(f, nbuf);
  if(inTrans) fsl_db_transaction_rollback(db);
  return rc;
}

fsl_hash_types_e fsl_validate_hash(const char *zHash, int nHash){
  /* fossil(1) counterpart: hname_validate() */
  fsl_hash_types_e rc;
  switch(nHash){
    case FSL_STRLEN_SHA1: rc = FSL_HTYPE_SHA1; break;
    case FSL_STRLEN_K256: rc = FSL_HTYPE_K256; break;
    default: return FSL_HTYPE_ERROR;
  }
  return fsl_validate16(zHash, (fsl_size_t)nHash) ? rc : FSL_HTYPE_ERROR;
}

const char * fsl_hash_type_name(fsl_hash_types_e h, const char *zUnknown){
  /* fossil(1) counterpart: hname_alg() */
  switch(h){
    case FSL_HTYPE_SHA1: return "SHA1";
    case FSL_HTYPE_K256: return "SHA3-256";
    default: return zUnknown;
  }
}

fsl_hash_types_e fsl_verify_blob_hash(fsl_buffer const * pIn,
                                      const char *zHash, int nHash){
  fsl_hash_types_e id = FSL_HTYPE_ERROR;
  switch(nHash){
    case FSL_STRLEN_SHA1:{
      fsl_sha1_cx cx;
      char hex[FSL_STRLEN_SHA1+1] = {0};
      fsl_sha1_init(&cx);
      fsl_sha1_update(&cx, pIn->mem, (unsigned)pIn->used);
      fsl_sha1_final_hex(&cx, hex);
      if(0==memcmp(hex, zHash, FSL_STRLEN_SHA1)){
        id = FSL_HTYPE_SHA1;
      }
      break;
    }
    case FSL_STRLEN_K256:{
      fsl_sha3_cx cx;
      unsigned char const * hex;
      fsl_sha3_init(&cx);
      fsl_sha3_update(&cx, pIn->mem, (unsigned)pIn->used);
      hex = fsl_sha3_end(&cx);
      if(0==memcmp(hex, zHash, FSL_STRLEN_K256)){
        id = FSL_HTYPE_K256;
      }
      break;
    }
    default:
      break;
  }
  return id;
}
#undef MARKER
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/cx.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 houses most of the context-related APIs.
*/
#include "fossil-scm/fossil-internal.h"
#include "fossil-scm/fossil-checkout.h"
#include "fossil-scm/fossil-confdb.h"
#include "fossil-scm/fossil-hash.h"
#include "fossil-ext_regexp.h"
#include "sqlite3.h"
#include <assert.h>

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

#include <stdlib.h>
#include <string.h>

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

/** Number of fsl_cx::scratchpads buffers. */
#define FSL_CX_NSCRATCH \
  ((int)(sizeof(fsl_cx_empty.scratchpads.buf) \
         /sizeof(fsl_cx_empty.scratchpads.buf[0])))
const int StaticAssert_scratchpadsCounts[
     (FSL_CX_NSCRATCH==
      ((int)(sizeof(fsl_cx_empty.scratchpads.used)
             /sizeof(fsl_cx_empty.scratchpads.used[0]))))
     ? 1 : -1
];
/**
   Used for setup and teardown of sqlite3_auto_extension().
*/
static volatile long sg_autoregctr = 0;

int fsl_cx_init( fsl_cx ** tgt, fsl_cx_init_opt const * param ){
  static fsl_cx_init_opt paramDefaults = fsl_cx_init_opt_default_m;
  int rc = 0;
  fsl_cx * f;
  extern int fsl_cx_install_timeline_crosslinkers(fsl_cx *f);
  if(!tgt) return FSL_RC_MISUSE;
  else if(!param){
    if(!paramDefaults.output.state.state){
      paramDefaults.output.state.state = stdout;
    }
    param = &paramDefaults;
  }
  if(*tgt){
    void const * allocStamp = (*tgt)->allocStamp;
    fsl_cx_reset(*tgt, 1) /* just to be safe */;
    f = *tgt;
    *f = fsl_cx_empty;
    f->allocStamp = allocStamp;
  }else{
    f = fsl_cx_malloc();
    if(!f) return FSL_RC_OOM;

    *tgt = f;
  }
  f->output = param->output;
  f->cxConfig = param->config;

  enum {
    /* Because testing shows a lot of re-allocs via some of the
       lower-level stat()-related bits, we pre-allocate this many
       bytes into f->scratchpads.buf[].  Curiously, there is almost no
       difference in (re)allocation behaviour until this size goes
       above about 200.

       We ignore allocation errors here, as they're not critical (but
       upcoming ops will fail when _they_ run out of memory).
    */
    InitialScratchCapacity = 256
  };
  assert(FSL_CX_NSCRATCH
         == (sizeof(f->scratchpads.used)/sizeof(f->scratchpads.used[0])));
  for(int i = 0; i < FSL_CX_NSCRATCH; ++i){
    f->scratchpads.buf[i] = fsl_buffer_empty;
    f->scratchpads.used[i] = false;
    fsl_buffer_reserve(&f->scratchpads.buf[i], InitialScratchCapacity);
  }
  /* We update f->error.msg often, so go ahead and pre-allocate that, too,
     also ignoring any OOM error at this point. */
  fsl_buffer_reserve(&f->error.msg, InitialScratchCapacity);

#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0
  sqlite3_initialize(); /*the SQLITE_MUTEX_STATIC_MASTER will not cause autoinit of sqlite for some reason*/
  sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
#endif
    if ( 1 == ++sg_autoregctr )
    {
      /*register our statically linked extensions to be auto-init'ed at the appropriate time*/
      sqlite3_auto_extension((void(*)(void))(sqlite3_regexp_init));     /*sqlite regexp extension*/
      atexit(sqlite3_reset_auto_extension)
        /* Clean up pseudo-leak valgrind complains about:
           https://www.sqlite.org/c3ref/auto_extension.html */;
    }
#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0
  sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
#endif
  f->dbMem.f = f /* so that dbMem gets fsl_xxx() SQL funcs installed. */;
  rc = fsl_db_open( &f->dbMem, "", 0 );
  if(!rc){
    /* Attempt to ensure that the TEMP tables/indexes use FILE storage. */
    rc = fsl_db_exec(&f->dbMem, "PRAGMA temp_store=FILE;");
  }
  if(!rc){
    rc = fsl_cx_install_timeline_crosslinkers(f);
  }
  if(rc){
    if(f->dbMem.error.code){
      fsl_cx_uplift_db_error(f, &f->dbMem);
    }
  }else{
    f->dbMain = &f->dbMem;
    f->dbMem.role = FSL_DBROLE_MAIN;
  }
  return rc;
}

static void fsl_cx_mcache_clear(fsl_cx *f){
  const unsigned cacheLen =
    (unsigned)(sizeof(fsl_mcache_empty.aAge)
               /sizeof(fsl_mcache_empty.aAge[0]));
  for(unsigned i = 0; i < cacheLen; ++i){
    fsl_deck_finalize(&f->cache.mcache.decks[i]);
  }
  f->cache.mcache = fsl_mcache_empty;
}

void fsl_cx_reset(fsl_cx * f, bool closeDatabases){
  fsl_checkin_discard(f);
#define SFREE(X) fsl_free(X); X = NULL
  if(closeDatabases){
    fsl_cx_close_dbs(f);
    /*
      Reminder: f->dbMem is NOT closed here: it's an internal detail,
      not public state. We could arguably close and reopen it here,
      but then we introduce a potenital error case (OOM) where we
      currently have none (thus the void return).
    */
    SFREE(f->ckout.dir);
    f->ckout.dirLen = 0;
    /* assert(NULL==f->dbMain); */
    assert(!f->repo.db.dbh);
    assert(!f->ckout.db.dbh);
    assert(!f->config.db.dbh);
    assert(!f->repo.db.filename);
    assert(!f->ckout.db.filename);
    assert(!f->config.db.filename);
  }
  SFREE(f->repo.user);
  SFREE(f->ckout.uuid);
  SFREE(f->cache.projectCode);
#undef SFREE
  fsl_error_clear(&f->error);
  fsl_card_J_list_free(&f->ticket.customFields, 1);
  fsl_buffer_clear(&f->fileContent);
  for(int i = 0; i < FSL_CX_NSCRATCH; ++i){
    fsl_buffer_clear(&f->scratchpads.buf[i]);
    f->scratchpads.used[i] = false;
  }
  fsl_acache_clear(&f->cache.arty);
  fsl_id_bag_clear(&f->cache.leafCheck);
  fsl_id_bag_clear(&f->cache.toVerify);
  fsl_cx_clear_mf_seen(f);
#define SLIST(L) fsl_list_visit_free(L, 1)
#define GLOBL(X) SLIST(&f->cache.globs.X)
  GLOBL(ignore);
  GLOBL(binary);
  GLOBL(crnl);
#undef GLOBL
#undef SLIST
  fsl_cx_mcache_clear(f);
  f->cache = fsl_cx_empty.cache;
}

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

void fsl_cx_finalize( fsl_cx * f ){
  void const * allocStamp = f ? f->allocStamp : NULL;
  if(!f) return;

  if(f->xlinkers.list){
#if 0
    /* Potential TODO: add client-specified finalizer for xlink
       callback state, using a fsl_state to replace the current
       (void*) for x->state. Seems like overkill for the time being.
     */
    fsl_size_t i;
    for( i = 0; i < f->xlinkers.used; ++i ){
      fsl_xlinker * x = f->xlinkers.list + i;
      if(x->state.finalize.f){
        x->state.finalize.f(x->state.finalize.state, x->state.state);
      }
    }
#endif    
    fsl_free(f->xlinkers.list);
    f->xlinkers = fsl_xlinker_list_empty;
  }

  if(f->clientState.finalize.f){
    f->clientState.finalize.f( f->clientState.finalize.state,
                               f->clientState.state );
  }
  f->clientState = fsl_state_empty;
  if(f->output.state.finalize.f){
    f->output.state.finalize.f( f->output.state.finalize.state,
                                f->output.state.state );
  }
  f->output = fsl_outputer_empty;
  fsl_cx_reset(f, 1);
  fsl_db_close(&f->dbMem);
  *f = fsl_cx_empty;
  if(&fsl_cx_empty == allocStamp){
    fsl_free(f);
  }else{
    f->allocStamp = allocStamp;
  }

  /* clean up the auto extension; not strictly necessary, but pleases debug malloc's */
#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0
  sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
#endif
  if ( 0 == --sg_autoregctr )
  {
    /*register our statically linked extensions to be auto-init'ed at the appropriate time*/
    sqlite3_cancel_auto_extension((void(*)(void))(sqlite3_regexp_init));     /*sqlite regexp extension*/
  }
  assert(sg_autoregctr>=0);
#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0
  sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
#endif
}

void fsl_cx_err_reset(fsl_cx * f){
  if(f){
    fsl_error_reset(&f->error);
    fsl_db_err_reset(&f->dbMem);
    fsl_db_err_reset(&f->repo.db);
    fsl_db_err_reset(&f->config.db);
    fsl_db_err_reset(&f->ckout.db);
  }
}

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

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

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

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

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

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

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

int fsl_cx_uplift_db_error( fsl_cx * f, fsl_db * db ){
  assert(f);
  if(!f) return FSL_RC_MISUSE;
  if(!db){
    db = f->dbMain;
    assert(db && "misuse: no DB handle to uplift error from!");
    if(!db) return FSL_RC_MISUSE;
  }
  fsl_error_move( &db->error, &f->error );
  return f->error.code;
}

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

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

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

fsl_db * fsl_needs_repo(fsl_cx * f){
  fsl_db * const db = fsl_cx_db_repo(f);
  if(!db){
    fsl_cx_err_set(f, FSL_RC_NOT_A_REPO,
                   "Fossil context has no opened repository db.");
  }
  return db;
}

fsl_db * fsl_needs_ckout(fsl_cx * f){
  fsl_db * const db = fsl_cx_db_ckout(f);
  if(!db){
    fsl_cx_err_set(f, FSL_RC_NOT_A_CKOUT,
                   "Fossil context has no opened checkout db.");
  }
  return db;
}

fsl_db * fsl_cx_db_ckout( fsl_cx * f ){
  if(!f) return NULL;
  else if(f->ckout.db.dbh) return &f->ckout.db;
  else if(f->dbMain && (FSL_DBROLE_CKOUT & f->dbMain->role)) return f->dbMain;
  else return NULL;
}

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

    Returns one of f->db{Config,Repo,Ckout,Mem}
    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 * f, fsl_dbrole_e r){
  switch(r){
    case FSL_DBROLE_CONFIG:
      return &f->config.db;
    case FSL_DBROLE_REPO:
      return &f->repo.db;
    case FSL_DBROLE_CKOUT:
      return &f->ckout.db;
    case FSL_DBROLE_MAIN:
      return &f->dbMem;
    case FSL_DBROLE_NONE:
    default:
      return NULL;
  }
}

/**
    Detaches the given db role from f->dbMain and removes the role
    from f->dbMain->role.
*/
static int fsl_cx_detach_role(fsl_cx * f, fsl_dbrole_e r){
  if(!f || !f->dbMain) return FSL_RC_MISUSE;
  else if(!(r & f->dbMain->role)){
    assert(!"Misuse: cannot detach unattached role.");
    return FSL_RC_NOT_FOUND;
  }
  else{
    fsl_db * db = fsl_cx_db_for_role(f,r);
    int rc;
    if(!db) return FSL_RC_RANGE;
    assert(f->dbMain != db);
    f->dbMain->role &= ~r;
    rc = fsl_db_detach( f->dbMain, fsl_db_role_label(r) );
    //MARKER(("rc=%s %s %s\n", fsl_rc_cstr(rc), fsl_db_role_label(r),
    //        fsl_buffer_cstr(&f->dbMain->error.msg)));
    fsl_free(db->filename);
    fsl_free(db->name);
    db->filename = NULL;
    db->name = NULL;
    return rc;
  }
}


/** @internal

    Attaches the given db file to f with the given role. This function "should"
    be static but we need it in fsl_repo.c when creating a new repository.
*/
int fsl_cx_attach_role(fsl_cx * f, const char *zDbName, fsl_dbrole_e r){
  char const * label = fsl_db_role_label(r);
  fsl_db * db = fsl_cx_db_for_role(f, r);
  char ** nameDest = NULL;
  int rc;
  if(!f->dbMain){
    assert(!"Misuse: f->dbMain has not been set: cannot attach role.");
    return FSL_RC_MISUSE;
  }
  else if(r & f->dbMain->role){
    assert(!"Misuse: role is already attached.");
    return fsl_cx_err_set(f, FSL_RC_MISUSE,
                          "Db role %s is already attached.",
                          label);                          
  }
#if 0
  MARKER(("r=%s db=%p, ckout.db=%p\n", label,
          (void*)db, (void*)&f->ckout.db));
  MARKER(("r=%s db=%p, repo.db=%p\n", label,
          (void*)db, (void*)&f->repo.db));
  MARKER(("r=%s db=%p, dbMain=%p\n", label,
          (void*)db, (void*)f->dbMain));
#endif
  assert(db);
  assert(label);
  assert(f->dbMain != db);
  assert(!db->filename);
  assert(!db->name);
  nameDest = &db->filename;
  switch(r){
    case FSL_DBROLE_CONFIG:
    case FSL_DBROLE_REPO:
    case FSL_DBROLE_CKOUT:
      break;
    case FSL_DBROLE_MAIN:
    case FSL_DBROLE_NONE:
    default:
      assert(!"cannot happen/not legal");
      return FSL_RC_RANGE;
  }
  *nameDest = fsl_strdup(zDbName);
  db->name = *nameDest ? fsl_strdup(label) : NULL;
  if(!db->name){
    rc = FSL_RC_OOM;
    /* Design note: we do the strdup() before the ATTACH because if
       the attach succeeds and strdup fails, detaching the db will
       almost certainly fail because it must allocate for its prepared
       statement and other internals. We would end up having to leave
       the db attached and returning a failure, which could lead to a
       memory leak (or worse) downstream.
    */
  }else{
    /*MARKER(("Attached %p role %d %s %s\n",
      (void const *)db, r, db->name, db->filename));*/
    rc = fsl_db_attach(f->dbMain, zDbName, label);
    if(rc){
      fsl_cx_uplift_db_error(f, f->dbMain);
    }else{
      //MARKER(("Attached db %p %s from %s\n",
      //  (void*)db, label, db->filename));
      f->dbMain->role |= r;
    }
  }
  return rc;
}


/*
  2020-03-04: this function has, since the switch to using ATTACH for
  all major dbs, largely been supplanted by fsl_cx_attach_role(), but
  should we ever return to a multi-db-handle world, this
  implementation (or something close to it) is what we'll want.

  Analog to fossil's db_open_or_attach().
  
  This function is still very much up for reconsideration. i'm not
  terribly happy with the "db roles" here - i'd prefer to have them
  each in their own struct, but understand that the roles may play a
  part in query generation, so i haven't yet ruled them out. On
  20140724 we got a fix which allows us to publish concrete names for
  all dbs, regardless of which one is the "main", so much of the
  reason for that concern has been alleviated. In late October, 2014,
  Dave figured out that multi-attach leads to locking problems, so
  the innards are being rewritten to use a :memory: db as the
  permanent "main", and attaching the others.

  Opens or attaches the given db file. zDbName is the
  file name of the DB. role is the role of that db
  in the framework (that determines its db name in SQL).
  
  The first time this is called, f->dbMain is set to point
  to fsl_cx_db_for_role(f, role).
  
  If f->dbMain is already set then the db is attached using a
  role-dependent name and role is added to f->dbMain->role's bitmask.
  
  If f->dbMain is not open then it is opened here and its role is set
  to the given role. It is _also_ ATTACHED using its role name. This
  allows us to publish the names of all Fossil-managed db handles
  regardless of which one is "main". e.g. if repo is opened first, it
  is addressable from SQL as both "main" or "repo".
  
  The given db zDbName is the name of a database file.  If f->dbMain
  is not opened then that db is assigned to the given db file,
  otherwise attach attach zDbName using the name zLabel.
  
  If pWasAttached is not NULL then it is set to a true value if the
  db gets attached, else a false value. It is only modified on success.
  
  It is an error to open the same db role more than once, and trying
  to do so results in a FSL_RC_ACCESS error (f's error state is
  updated).
*/
static int fsl_cx_db_main_open_or_attach( fsl_cx * f,
                                          const char *zDbName,
                                          /* const char *zLabel, */
                                          fsl_dbrole_e role,
                                          int *pWasAttached){
  int rc;
  int wasAttached = 0;
  fsl_db * db;
  assert(f);
  assert(zDbName && *zDbName);
  assert((FSL_DBROLE_CONFIG==role)
         || (FSL_DBROLE_CKOUT==role)
         || (FSL_DBROLE_REPO==role));
  if(!f || !zDbName) return FSL_RC_MISUSE;
  else if((FSL_DBROLE_NONE==role) || !*zDbName) return FSL_RC_RANGE;
  switch(role){
    case FSL_DBROLE_REPO:
    case FSL_DBROLE_CKOUT:
    case FSL_DBROLE_CONFIG:
      db = fsl_cx_db_for_role(f, role);
      break;
    default:
      assert(!"not possible");
      db = NULL;
      /* We'll fall through and segfault in a moment... */
  }
  assert(db);
  try_again:
  if(!f->dbMain) {
    assert(!"Not possible since moving to a :memory: main db.");
    /* This is the first db. It is now our main db. */
    assert( FSL_DBROLE_NONE==db->role );
    assert( NULL==db->dbh );
    assert( role == FSL_DBROLE_CONFIG
            || role == FSL_DBROLE_REPO 
            || role == FSL_DBROLE_CKOUT );
    f->dbMain = db;
    db->f = f;
    rc = fsl_db_open( db, zDbName, FSL_OPEN_F_RW );
    if(!rc){
      db->role = role;
      assert(!db->name);
#if 0
      db->name = fsl_strdup( fsl_db_role_label(FSL_DBROLE_MAIN) );
      if(!db->name) rc = FSL_RC_OOM;
#else
      /* Many thanks to Simon Slavin, native resident of the sqlite3
         mailing list, for this... we apply the db's "real" name via
         an ATTACH, meaning that all the client-side kludgery to get the
         proper DB name is now obsolete.
      */
      rc = fsl_db_attach( db, zDbName, fsl_db_role_label(role) );
      if(!rc){
        db->name = fsl_strdup( fsl_db_role_label(role) );
        if(!db->name) rc = FSL_RC_OOM;
      }
#endif
    }
    /* MARKER(("db->role=%d\n",db->role)); */
    /* g.db = fsl_db_open(zDbName); */
    /* g.zMainDbType = zLabel; */
    /* f->mainDbType = zLabel; */
    /* f->dbMain.role = FSL_DBROLE_MAIN; */
  }else{
    /* Main db has already been opened. Attach this one to it... */
    assert( (int)FSL_DBROLE_NONE!=f->dbMain->role );
    assert( f == f->dbMain->f );
    if(NULL == f->dbMain->dbh){
      /* It was closed in the meantime. Re-assign dbMain... */
      MARKER(("Untested: re-assigning f->dbMain after it has been closed.\n"));
      assert(!"Broken by addition of f->dbMem.");
      f->dbMain = NULL;
      goto try_again;
    }
    if((int)role == db->role){
      return fsl_cx_err_set(f, FSL_RC_ACCESS,
                            "Cannot open/attach db role "
                            "#%d (%s) more than once.",
                            (int)role, fsl_db_role_label(role));
    }
    rc = fsl_cx_attach_role(f, zDbName, role);
    wasAttached = 1;
  }
  if(!rc){
    if(pWasAttached) *pWasAttached = wasAttached;
  }else{
    if(db->error.code){
      fsl_cx_uplift_db_error(f, db);
    }
    if(db==f->dbMain) f->dbMain = NULL;
    fsl_db_close(db);
  }
  return rc;
}

int fsl_config_close( fsl_cx * f ){
  if(!f) return FSL_RC_MISUSE;
  else{
    int rc;
    fsl_db * db = &f->config.db;
    if(db->dbh){
      /* Config is our main db. Close them all */
      assert(!"Not possible since f->dbMem added.");
      assert(f->dbMain==db);
      fsl_ckout_close(f);
      fsl_repo_close(f);
      rc = fsl_db_close(db);
      f->dbMain = NULL;
    }else if(f->dbMain && (FSL_DBROLE_CONFIG & f->dbMain->role)){
      /* Config db is ATTACHed. */
      assert(f->dbMain!=db);
      rc = fsl_cx_detach_role(f, FSL_DBROLE_CONFIG);
    }
    else rc = FSL_RC_NOT_FOUND;
    assert(!db->dbh);
    fsl_db_clear_strings(db, 1);
    return rc;
  }
}

int fsl_repo_close( fsl_cx * f ){
  if(fsl_cx_transaction_level(f)){
    return fsl_cx_err_set(f, FSL_RC_MISUSE,
                          "Cannot close repo with opened transaction.");
  }else{
    int rc;
    fsl_db * db = &f->repo.db;
    if(db->dbh){
      /* Repo is our main db. Close them all */
      assert(!"Not possible since f->dbMem added.");
      assert(f->dbMain==db);
      fsl_config_close(f);
      fsl_ckout_close(f);
      rc = fsl_db_close(db);
      f->dbMain = NULL;
    }else if(f->dbMain && (FSL_DBROLE_REPO & f->dbMain->role)){
      /* Repo db is ATTACHed. */
      if(FSL_DBROLE_CKOUT & f->dbMain->role){
        rc = fsl_cx_err_set(f, FSL_RC_MISUSE,
                            "Cannot close repo while checkout is "
                            "opened.");
      }else{
        assert(f->dbMain!=db);
        rc = fsl_cx_detach_role(f, FSL_DBROLE_REPO);
      }
    }
    else rc = FSL_RC_NOT_FOUND;
    assert(!db->dbh);
    fsl_db_clear_strings(db, true);
    return rc;
  }
}

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

          If the repo is dbMain:

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

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

          But that's all hypothetical - best we make it impossible
          for the public API to have a checkout without a repo
          or a repo/checkout mismatch.
        */
        ;
#endif
    if(db->dbh){
      /* checkout is our main db. Close them all. */
      assert(!"Not possible since f->dbMem added.");
      assert(!f->repo.db.dbh);
      assert(f->dbMain==db);
      fsl_repo_close(f);
      fsl_config_close(f);
      rc = fsl_db_close(db);
      f->dbMain = NULL;
    }
    else if(f->dbMain && (FSL_DBROLE_CKOUT & f->dbMain->role)){
      /* Checkout db is ATTACHed. */
      assert(f->dbMain!=db);
      rc = fsl_cx_detach_role(f, FSL_DBROLE_CKOUT);
      fsl_repo_close(f) /*
                          Because the repo is implicitly opened, we
                          "should" implicitly close it. This is
                          debatable but "probably almost always"
                          desired. i can't currently envisage a
                          reasonable use-case which requires closing
                          the checkout but keeping the repo opened.
                          The repo can always be re-opened by itself.
                        */;
    }
    else{
      rc = FSL_RC_NOT_FOUND;
    }
    fsl_free(f->ckout.uuid);
    f->ckout.uuid = NULL;
    f->ckout.rid = 0;
    assert(!db->dbh);
    fsl_db_clear_strings(db, true);
    return rc;
  }
}

/**
   If zDbName is a valid checkout database file, open it and return 0.
   If it is not a valid local database file, return a non-0 code.
*/
static int fsl_cx_ckout_open_db(fsl_cx * f, const char *zDbName){
  /* char *zVFileDef; */
  int rc;
  fsl_int_t const lsize = fsl_file_size(zDbName);
  if( -1 == lsize  ){
    return FSL_RC_NOT_FOUND /* might be FSL_RC_ACCESS? */;
  }
  if( lsize%1024!=0 || lsize<4096 ){
    return fsl_cx_err_set(f, FSL_RC_RANGE,
                          "File's size is not correct for a "
                          "checkout db: %s",
                          zDbName);
  }
  rc = fsl_cx_db_main_open_or_attach(f, zDbName,
                                     FSL_DBROLE_CKOUT,
                                     NULL);
  return rc;
#if 0
  /* Historical bits: no longer needed(?). */

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

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

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


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

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

/**
    Passes the fsl_schema_config() SQL code through a new/truncated
    file named dbName. If the file exists before this call, it is
    unlink()ed and fails if that operation fails.

    FIXME: this was broken by the addition of "cfg." prefix on the
    schema's tables.
 */
static int fsl_config_file_reset(fsl_cx * f, char const * dbName){
  fsl_db DB = fsl_db_empty;
  fsl_db * db = &DB;
  int rc = 0;
  bool isAttached = false;
  const char * zPrefix = fsl_db_role_label(FSL_DBROLE_CONFIG);
  if(-1 != fsl_file_size(dbName)){
    rc = fsl_file_unlink(dbName);
    if(rc){
      return fsl_cx_err_set(f, rc,
                            "Error %s while removing old config file (%s)",
                            fsl_rc_cstr(rc), dbName);
    }
  }
  /**
     Hoop-jumping: because the schema file has a cfg. prefix for the
     table(s), and we cannot assign an arbitrary name to an open()'d
     db, we first open the db (making the the "main" db), then
     ATTACH it to itself to provide the fsl_db_role_label() alias.
  */
  rc = fsl_db_open(db, dbName, FSL_OPEN_F_RWC);
  if(rc) goto end;
  rc = fsl_db_attach(db, dbName, zPrefix);
  if(rc) goto end;
  isAttached = true;
  rc = fsl_db_exec_multi(db, "%s", fsl_schema_config());
  end:
  rc = fsl_cx_uplift_db_error2(f, db, rc);
  if(isAttached) fsl_db_detach(db, zPrefix);
  fsl_db_close(db);
  return rc;
}

int fsl_config_global_preferred_name(char ** zOut){
  char * zEnv = 0;
  char * zRc = 0;
  int rc = 0;
  fsl_buffer buf = fsl_buffer_empty;

#if FSL_PLATFORM_IS_WINDOWS
#  error "TODO: port in fossil(1) db.c:db_configdb_name() Windows bits"
#else
  
#endif

  /* Option 1: $FOSSIL_HOME/.fossil */
  zEnv = fsl_getenv("FOSSIL_HOME");
  if(zEnv){
    zRc = fsl_mprintf("%s/.fossil", zEnv);
    if(!zRc) rc = FSL_RC_OOM;
    goto end;
  }
  /* Option 2: if $HOME/.fossil exists, use that */  
  rc = fsl_find_home_dir(&buf, 0);
  if(rc) goto end;
  rc = fsl_buffer_append(&buf, "/.fossil", 8);
  if(rc) goto end;
  if(fsl_file_size(fsl_buffer_cstr(&buf))>1024*3){
    zRc = fsl_buffer_take(&buf);
    goto end;
  }
  /* Option 3: $XDG_CONFIG_HOME/fossil.db */
  fsl_filename_free(zEnv);
  zEnv = fsl_getenv("XDG_CONFIG_HOME");
  if(zEnv){
    zRc = fsl_mprintf("%s/fossil.db", zEnv);
    if(!zRc) rc = FSL_RC_OOM;
    goto end;
  }
  /* Option 4: If $HOME/.config is a directory,
     use $HOME/.config/fossil.db */
  buf.used -= 8 /* "/.fossil" */;
  buf.mem[buf.used] = 0;
  rc = fsl_buffer_append(&buf, "/.config", 8);
  if(rc) goto end;
  if(fsl_dir_check(fsl_buffer_cstr(&buf))>0){
    zRc = fsl_mprintf("%b/fossil.db", &buf);
    if(!zRc) rc = FSL_RC_OOM;
    goto end;
  }
  /* Option 5: fall back to $HOME/.fossil */
  buf.used -= 8 /* "/.config" */;
  buf.mem[buf.used] = 0;
  rc = fsl_buffer_append(&buf, "/.fossil", 8);
  if(!rc) zRc = fsl_buffer_take(&buf);

  end:
  if(zEnv) fsl_filename_free(zEnv);
  if(!rc){
    assert(zRc);
    *zOut = zRc;
  }
  fsl_buffer_clear(&buf);
  return rc;
}

int fsl_config_open( fsl_cx * f, char const * openDbName ){
  int rc = 0;
  const char * zDbName = 0;
  char * zPrefName = 0;
  bool useAttach = false /* TODO? Move this into a parameter?
                            Do we need the fossil behaviour? */;
  if(!f) return FSL_RC_MISUSE;
  else if(f->config.db.dbh){
    fsl_config_close(f);
  }
  if(openDbName && *openDbName){
    zDbName = openDbName;
  }else{
    rc = fsl_config_global_preferred_name(&zPrefName);
    if(rc) goto end;
    zDbName = zPrefName;
  }
  {
    fsl_int_t const fsize = fsl_file_size(zDbName);
    if( -1==fsize || (fsize<1024*3) ){
      rc = fsl_config_file_reset(f, zDbName);
      if(rc) goto end;
    }
  }
#if defined(_WIN32) || defined(__CYGWIN__)
  /* TODO: Jan made some changes in this area in fossil(1) in
     January(?) 2014, such that only the config file needs to be
     writable, not the directory. Port that in.
  */
  if( fsl_file_access(zDbName, W_OK) ){
    rc = fsl_cx_err_set(f, FSL_RC_ACCESS,
                        "Configuration database [%s] "
                        "must be writeable.", zDbName);
    goto end;
  }
#endif

  if( useAttach ){
    rc = fsl_cx_attach_role(f, zDbName, FSL_DBROLE_CONFIG);
  }else{
    rc = fsl_cx_db_main_open_or_attach(f, zDbName,
                                       FSL_DBROLE_CONFIG,
                                       NULL);
    /* rc = fsl_db_open(&f->config.db, zDbName, FSL_OPEN_F_RWC ); */
  }
  if(!rc && !f->dbMain){
    assert(!"Not possible(?) since addition of f->dbMem");
    f->dbMain = &f->config.db;
  }
  end:
  fsl_free(zPrefName);
  return rc;
}

static void fsl_cx_username_from_repo(fsl_cx * f){
  fsl_db * dbR = fsl_cx_db_repo(f);
  char * u;
  assert(dbR);
  u = fsl_db_g_text(fsl_cx_db_repo(f), NULL,
                    "SELECT login FROM user WHERE uid=1");
  if(u){
    fsl_free(f->repo.user);
    f->repo.user = u;
  }
}

static int fsl_cx_load_glob_lists(fsl_cx * f){
  int rc;
  rc = fsl_config_globs_load(f, &f->cache.globs.ignore, "ignore-glob");
  if(!rc) rc = fsl_config_globs_load(f, &f->cache.globs.binary, "binary-glob");
  if(!rc) rc = fsl_config_globs_load(f, &f->cache.globs.crnl, "crnl-glob");
  return rc;
}

/**
   To be called after a repo or checkout/repo combination has been
   opened. This updates some internal cached info based on the
   checkout and/or repo.
*/
static int fsl_cx_after_open(fsl_cx * f){
  int rc = fsl_ckout_version_fetch(f);
  if(!rc) rc = fsl_cx_load_glob_lists(f);
  return rc;
}


static void fsl_cx_fetch_hash_policy(fsl_cx * f){
  int const iPol =
    fsl_config_get_int32( f, FSL_CONFDB_REPO,
                          FSL_HPOLICY_AUTO, "hash-policy");
  fsl_hashpolicy_e p;
  switch(iPol){
    case FSL_HPOLICY_SHA3: p = FSL_HPOLICY_SHA3; break;
    case FSL_HPOLICY_SHA3_ONLY: p = FSL_HPOLICY_SHA3_ONLY; break;
    case FSL_HPOLICY_SHA1: p = FSL_HPOLICY_SHA1; break;
    case FSL_HPOLICY_SHUN_SHA1: p = FSL_HPOLICY_SHUN_SHA1; break;
    default: p = FSL_HPOLICY_AUTO; break;
  }
  f->cxConfig.hashPolicy = p;
}

int fsl_repo_open( fsl_cx * f, char const * repoDbFile/* , bool readOnlyCurrentlyIgnored */ ){
  if(!f || !repoDbFile || !*repoDbFile) return FSL_RC_MISUSE;
  else if(fsl_cx_db_repo(f)){
    return fsl_cx_err_set(f, FSL_RC_ACCESS,
                          "Context already has an opened repository.");
  }
  else {
    int rc;
    if(0!=fsl_file_access( repoDbFile, F_OK )){
      rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                          "Repository db [%s] not found or cannot be read.",
                          repoDbFile);
    }else{
      rc = fsl_cx_db_main_open_or_attach(f, repoDbFile,
                                         FSL_DBROLE_REPO,
                                         NULL);
      if(!rc && !(FSL_CX_F_IS_OPENING_CKOUT & f->flags)){
        rc = fsl_cx_after_open(f);
      }
      if(!rc){
        fsl_db * const db = fsl_cx_db_repo(f);
        fsl_cx_username_from_repo(f);
        f->cache.allowSymlinks =
          fsl_config_get_bool(f, FSL_CONFDB_REPO,
                              false,
                              "allow-symlinks");
        f->cache.seenDeltaManifest =
          fsl_config_get_int32(f, FSL_CONFDB_REPO, -1,
                               "seen-delta-manifest");
        fsl_cx_fetch_hash_policy(f);
        if(f->cxConfig.hashPolicy==FSL_HPOLICY_AUTO){
          if(fsl_db_exists(db, "SELECT 1 FROM blob WHERE length(uuid)>40")
             || !fsl_db_exists(db, "SELECT 1 FROM blob WHERE length(uuid)==40")){
            f->cxConfig.hashPolicy = FSL_HPOLICY_SHA3;
          }
        }
      }
    }
    return rc;
  }
}

/**
    Tries to open the repository from which the current checkout
    derives. Returns 0 on success.
*/
static int fsl_repo_open_for_ckout(fsl_cx * f){
  char * repoDb = NULL;
  int rc;
  fsl_buffer nameBuf = fsl_buffer_empty;
  fsl_db * db = fsl_cx_db_ckout(f);
  assert(f);
  assert(f->ckout.dir);
  assert(db);
  rc = fsl_db_get_text(db, &repoDb, NULL,
                       "SELECT value FROM vvar "
                       "WHERE name='repository'");
  if(rc) fsl_cx_uplift_db_error( f, db );
  else if(repoDb){
    if(!fsl_is_absolute_path(repoDb)){
      /* Make it relative to the checkout db dir */
      rc = fsl_buffer_appendf(&nameBuf, "%s/%s", f->ckout.dir, repoDb);
      fsl_free(repoDb);
      if(rc) {
        fsl_buffer_clear(&nameBuf);
        return rc;
      }
      repoDb = (char*)nameBuf.mem /* transfer ownership */;
      nameBuf = fsl_buffer_empty;
    }
    rc = fsl_file_canonical_name(repoDb, &nameBuf, 0);
    fsl_free(repoDb);
    if(!rc){
      repoDb = fsl_buffer_str(&nameBuf);
      assert(repoDb);
      rc = fsl_repo_open(f, repoDb);
    }
    fsl_buffer_reserve(&nameBuf, 0);
  }else{
    /* This can only happen if we are not using a proper
       checkout db or someone has removed the repo link.
    */
    rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                        "Could not determine this checkout's "
                        "repository db file.");
  }
  return rc;
}

static void fsl_ckout_mtime_set(fsl_cx * const f){
  f->ckout.mtime = f->ckout.rid>0
    ? fsl_db_g_double(fsl_cx_db_repo(f), 0.0,
                      "SELECT mtime FROM event "
                      "WHERE objid=%" FSL_ID_T_PFMT,
                      f->ckout.rid)
    : 0.0;
}

int fsl_ckout_version_fetch( fsl_cx *f ){
  fsl_id_t rid = 0;
  int rc = 0;
  fsl_db * dbC = fsl_cx_db_ckout(f);
  fsl_db * dbR = dbC ? fsl_needs_repo(f) : NULL;
  assert(!dbC || (dbC && dbR));
  fsl_free(f->ckout.uuid);
  f->ckout.rid = -1;
  f->ckout.uuid = NULL;
  f->ckout.mtime = 0.0;
  if(!dbC){
    return 0;
  }
  fsl_cx_err_reset(f);
  rid = fsl_config_get_id(f, FSL_CONFDB_CKOUT, -1, "checkout");
  //MARKER(("rc=%s rid=%d\n",fsl_rc_cstr(f->error.code), (int)rid));
  if(rid>0){
    f->ckout.uuid = fsl_rid_to_uuid(f, rid);
    if(!f->ckout.uuid){
      assert(f->error.code);
      if(!f->error.code){
        rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                            "Could not load UUID for RID %"FSL_ID_T_PFMT,
                            (fsl_id_t)rid);
      }
    }else{
      assert(fsl_is_uuid(f->ckout.uuid));
    }
    f->ckout.rid = rid;
    fsl_ckout_mtime_set(f);
  }else if(rid==0){
    /* This is a legal case not possible before libfossil (and only
       afterwards possible in fossil(1)) - an empty repo without an
       active checkin. [Much later:] that capability has since been
       removed from fossil.
    */
    f->ckout.rid = 0;
  }else{
    rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                        "Cannot determine checkout version.");
  }
  return rc;
}

/** @internal

    Sets f->ckout.rid to the given rid (which must be 0 or a valid
    RID) and f->ckout.uuid to a copy of the given uuid. If uuid is
    NULL and rid is not 0 then the uuid is fetched using
    fsl_rid_to_uuid(), else if uuid is not NULL then it is assumed to
    be the UUID for the given RID and is copies to f->ckout.uuid.

    Returns 0 on success, FSL_RC_OOM if copying uuid fails, or some
    error from fsl_rid_to_uuid() if that fails.

    Does not write the changes to disk. Use fsl_ckout_version_write()
    for that. That routine also calls this one, so there's no need to
    call both.
*/
static int fsl_cx_ckout_version_set(fsl_cx *f, fsl_id_t rid,
                                    fsl_uuid_cstr uuid){
  char * u = 0;
  assert(rid>=0);
  u = uuid
    ? fsl_strdup(uuid)
    : (rid ? fsl_rid_to_uuid(f, rid) : 0);
  if(rid && !u) return FSL_RC_OOM;
  f->ckout.rid = rid;
  fsl_free(f->ckout.uuid);
  f->ckout.uuid = u;
  fsl_ckout_mtime_set(f);
  return 0;
}

int fsl_ckout_version_write( fsl_cx *f, fsl_id_t vid,
                             fsl_uuid_cstr hash ){
  int rc = 0;
  if(!fsl_needs_ckout(f)) return FSL_RC_NOT_A_CKOUT;
  else if(vid<0){
    return fsl_cx_err_set(f, FSL_RC_MISUSE,
                          "Invalid vid for fsl_ckout_version_write()");
  }
  if(f->ckout.rid!=vid){
    rc = fsl_cx_ckout_version_set(f, vid, hash);
  }
  if(!rc){
    rc = fsl_config_set_id(f, FSL_CONFDB_CKOUT,
                           "checkout", f->ckout.rid);
    if(!rc){
      rc = fsl_config_set_text(f, FSL_CONFDB_CKOUT,
                               "checkout-hash", f->ckout.uuid);
    }
  }
  if(!rc){
    char * zFingerprint = 0;
    rc = fsl_repo_fingerprint_search(f, 0, &zFingerprint);
    if(!rc){
      rc = fsl_config_set_text(f, FSL_CONFDB_CKOUT,
                               "fingerprint", zFingerprint);
      fsl_free(zFingerprint);
    }
  }
  if(!rc){
    int const mode = vid ? -1 : 0;
    rc = fsl_ckout_manifest_write(f, mode, mode, mode, 0);
  }
  return rc;
}

void fsl_ckout_version_info(fsl_cx *f, fsl_id_t * rid, fsl_uuid_cstr * uuid ){
  if(uuid) *uuid = f->ckout.uuid;
  if(rid) *rid = f->ckout.rid>=0 ? f->ckout.rid : 0;
}

int fsl_ckout_db_search( char const * dirName, bool checkParentDirs,
                         fsl_buffer * pOut ){
  int rc;
  fsl_int_t dLen = 0, i;
  enum { DbCount = 2 };
  const char aDbName[DbCount][10] = { "_FOSSIL_", ".fslckout" };
  fsl_buffer Buf = fsl_buffer_empty;
  fsl_buffer * buf = &Buf;
  buf->used = 0;
  if(dirName){
    dLen = fsl_strlen(dirName);
    if(0==dLen) return FSL_RC_RANGE;
    rc = fsl_buffer_reserve( buf, (fsl_size_t)(dLen + 10) );
    if(!rc) rc = fsl_buffer_append( buf, dirName, dLen );
    if(rc){
      fsl_buffer_clear(buf);
      return rc;
    }
  }else{
    char zPwd[4000];
    fsl_size_t pwdLen = 0;
    rc = fsl_getcwd( zPwd, sizeof(zPwd)/sizeof(zPwd[0]), &pwdLen );
    if(rc){
      fsl_buffer_clear(buf);
#if 0
      return fsl_cx_err_set(f, rc,
                            "Could not determine current directory. "
                            "Error code %d (%s).",
                            rc, fsl_rc_cstr(rc));
#else
      return rc;
#endif
    }
    if(1 == pwdLen && '/'==*zPwd) *zPwd = '.'
      /* When in the root directory (or chroot) then change dir name
         name to something we can use.
      */;
    rc = fsl_buffer_append(buf, zPwd, pwdLen);
    if(rc){
      fsl_buffer_clear(buf);
      return rc;
    }
    dLen = (fsl_int_t)pwdLen;
  }
  if(rc){
    fsl_buffer_clear(buf);
    return rc;
  }
  assert(buf->capacity>=buf->used);
  assert((buf->used == (fsl_size_t)dLen) || (1==buf->used && (int)'.'==(int)buf->mem[0]));
  assert(0==buf->mem[buf->used]);

  while(dLen>0){
    /*
      Loop over the list in aDbName, appending each one to
      the dir name in the search for something we can use.
    */
    fsl_int_t lenMarker = dLen /* position to re-set to on each
                                  sub-iteration. */ ;
    /* trim trailing slashes on this part, so that we don't end up
       with multiples between the dir and file in the final output. */
    while( dLen && ((int)'/'==(int)buf->mem[dLen-1])) --dLen;
    for( i = 0; i < DbCount; ++i ){
      char const * zName;
      buf->used = (fsl_size_t)lenMarker;
      dLen = lenMarker;
      rc = fsl_buffer_appendf( buf, "/%s", aDbName[i]);
      if(rc){
        fsl_buffer_clear(buf);
        return rc;
      }
      zName = fsl_buffer_cstr(buf);
      if(0==fsl_file_access(zName, 0)){
        if(pOut) rc = fsl_buffer_append( pOut, buf->mem, buf->used );
        fsl_buffer_clear(buf);
        return rc;
      }
      if(!checkParentDirs){
        dLen = 0;
        break;
      }else{
        /* Traverse up one dir and try again. */
        --dLen;
        while( dLen>0 && (int)buf->mem[dLen]!=(int)'/' ){ --dLen; }
        while( dLen>0 && (int)buf->mem[dLen-1]==(int)'/' ){ --dLen; }
        if(dLen>lenMarker){
          buf->mem[dLen] = 0;
        }
      }
    }
  }
  fsl_buffer_clear(buf);
  return FSL_RC_NOT_FOUND;
}

int fsl_cx_getcwd(fsl_cx * f, fsl_buffer * pOut){
  char cwd[FILENAME_MAX] = {0};
  fsl_size_t cwdLen = 0;
  int rc = fsl_getcwd(cwd, (fsl_size_t)sizeof(cwd), &cwdLen);
  if(rc){
    return fsl_cx_err_set(f, rc,
                          "Could not get current working directory!");
  }
  rc = fsl_buffer_append(pOut, cwd, cwdLen);
  return rc
    ? fsl_cx_err_set(f, rc/*must be an OOM*/, NULL)
    : 0;
}

int fsl_ckout_open_dir( fsl_cx * f, char const * dirName,
                        bool checkParentDirs ){
  int rc;
  fsl_buffer Buf = fsl_buffer_empty;
  fsl_buffer * buf = &Buf;
  char const * zName;
  if(fsl_cx_db_ckout(f)){
    return fsl_cx_err_set( f, FSL_RC_ACCESS,
                           "A checkout is already opened. "
                           "Close it before opening another.");
  }
  rc = fsl_ckout_db_search(dirName, checkParentDirs, buf);
  if(rc){
    if(FSL_RC_NOT_FOUND==rc){
      rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                          "Could not find checkout under [%s].",
                          dirName ? dirName : ".");
    }
    fsl_buffer_clear(buf);
    return rc;
  }
  assert(buf->used>1 /* "/<FILENAME>" */);
  zName = fsl_buffer_cstr(buf);
  rc = fsl_cx_ckout_open_db(f, zName);
  if(rc){
    fsl_buffer_clear(buf);
    return rc;
  }else{
    /* Checkout db is now opened. Fiddle some internal
       bits...
    */
    unsigned char * end = buf->mem+buf->used-1;
    /* Find dir part */
    while(end>buf->mem && (unsigned char)'/'!=*end) --end;
    assert('/' == (char)*end && "fsl_ckout_db_search() appends '/<DBNAME>'");
    fsl_free(f->ckout.dir);
    f->ckout.dirLen = end - buf->mem +1 /* for trailing '/' */ ;
    *(end+1) = 0; /* Rather than strdup'ing, we'll just lop off the
                     filename part. Keep the '/' for historical
                     conventions purposes - it simplifies path
                     manipulation later on. */
    f->ckout.dir = fsl_buffer_take(buf);
    assert(!f->ckout.dir[f->ckout.dirLen]);
    assert('/' == f->ckout.dir[f->ckout.dirLen-1]);
    f->flags |= FSL_CX_F_IS_OPENING_CKOUT;
    rc = fsl_repo_open_for_ckout(f);
    f->flags &= ~FSL_CX_F_IS_OPENING_CKOUT;
    if(!rc) rc = fsl_cx_after_open(f);
    if(rc){
      /* Is this sane? Is not doing it sane? */
      fsl_ckout_close(f);
    }
    return rc;
  }
}


char const * fsl_cx_db_file_for_role(fsl_cx const * f,
                                     fsl_dbrole_e r,
                                     fsl_size_t * len){
  fsl_db const * db = fsl_cx_db_for_role((fsl_cx*)f, r);
  char const * rc = db ? db->filename : NULL;
  if(len) *len = fsl_strlen(rc);
  return rc;
}

char const * fsl_cx_db_name_for_role(fsl_cx const * f,
                                     fsl_dbrole_e r,
                                     fsl_size_t * len){
  if(FSL_DBROLE_MAIN == r){
    /* special case to be removed when f->dbMem bits are
       finished. */
    if(len) *len=4;
    return "main";
  }else{
    fsl_db const * db = fsl_cx_db_for_role((fsl_cx*)f, r);
    char const * rc = db ? db->name : NULL;
    if(len) *len = rc ? fsl_strlen(rc) : 0;
    return rc;
  }
}

char const * fsl_cx_db_file_config(fsl_cx const * f,
                                   fsl_size_t * len){
  char const * rc = NULL;
  if(f && f->config.db.filename){
    rc = f->config.db.filename;
    if(len) *len = fsl_strlen(rc);
  }
  return rc;
}

char const * fsl_cx_db_file_repo(fsl_cx const * f,
                                 fsl_size_t * len){
  char const * rc = NULL;
  if(f && f->repo.db.filename){
    rc = f->repo.db.filename;
    if(len) *len = fsl_strlen(rc);
  }
  return rc;
}

char const * fsl_cx_db_file_ckout(fsl_cx const * f,
                                     fsl_size_t * len){
  char const * rc = NULL;
  if(f && f->ckout.db.filename){
    rc = f->ckout.db.filename;
    if(len) *len = fsl_strlen(rc);
  }
  return rc;
}

char const * fsl_cx_ckout_dir_name(fsl_cx const * f,
                                      fsl_size_t * len){
  char const * rc = NULL;
  if(f && f->ckout.dir){
    rc = f->ckout.dir;
    if(len) *len = f->ckout.dirLen;
  }
  return rc;
}

int fsl_cx_flags_get( fsl_cx * f ){
  return f->flags;
}

int fsl_cx_flag_set( fsl_cx * f, int flags, bool enable ){
  if(enable) f->flags |= flags;
  else f->flags &= ~flags;
  return f->flags;
}


fsl_xlinker * fsl_xlinker_by_name( fsl_cx * f, char const * name ){

  fsl_xlinker * rv = NULL;
  fsl_size_t i;
  for( i = 0; i < f->xlinkers.used; ++i ){
    rv = f->xlinkers.list + i;
    if(0==fsl_strcmp(rv->name, name)) return rv;
  }
  return NULL;
}

int fsl_xlink_listener( fsl_cx * f, char const * name,
                        fsl_deck_xlink_f cb, void * cbState ){
  fsl_xlinker * x;
  if(!f || !cb || !name || !*name) return FSL_RC_MISUSE;
  x = fsl_xlinker_by_name(f, name);
  if(x){
    /* Replace existing entry */
    x->f = cb;
    x->state = cbState;
    return 0;
  }else if(f->xlinkers.used <= f->xlinkers.capacity){
    /* Expand the array */
    fsl_size_t const n = f->xlinkers.used ? f->xlinkers.used * 2 : 5;
    fsl_xlinker * re =
      (fsl_xlinker *)fsl_realloc(f->xlinkers.list,
                                 n * sizeof(fsl_xlinker));
    if(!re) return FSL_RC_OOM;
    f->xlinkers.list = re;
    f->xlinkers.capacity = n;
  }
  x = f->xlinkers.list + f->xlinkers.used++;
  *x = fsl_xlinker_empty;
  x->f = cb;
  x->state = cbState;
  x->name = name;
  return 0;
}

int fsl_cx_user_set( fsl_cx * f, char const * userName ){
  if(!f) return FSL_RC_MISUSE;
  else if(!userName || !*userName){
    fsl_free(f->repo.user);
    f->repo.user = NULL;
    return 0;
  }else{
    char * u = fsl_strdup(userName);
    if(!u) return FSL_RC_OOM;
    else{
      fsl_free(f->repo.user);
      f->repo.user = u;
      return 0;
    }    
  }
}

char const * fsl_cx_user_get( fsl_cx const * f ){
  return f ? f->repo.user : NULL;
}

int fsl_cx_schema_ticket(fsl_cx * f, fsl_buffer * pOut){
  fsl_db * db = f ? fsl_needs_repo(f) : NULL;
  if(!f || !pOut) return FSL_RC_MISUSE;
  else if(!db) return FSL_RC_NOT_A_REPO;
  else{
    fsl_size_t const oldUsed = pOut->used;
    int rc = fsl_config_get_buffer(f, FSL_CONFDB_REPO,
                                   "ticket-table", pOut);
    if((FSL_RC_NOT_FOUND==rc)
       || (oldUsed == pOut->used/*found but it was empty*/)
       ){
      rc = fsl_buffer_append(pOut, fsl_schema_ticket(), -1);
    }
    return rc;
  }
}


int fsl_cx_stat2( fsl_cx * f, bool relativeToCwd,
                  char const * zName, fsl_fstat * tgt,
                  fsl_buffer * nameOut, bool fullPath){
  int rc;
  fsl_buffer * b = 0;
  fsl_buffer * bufRel = 0;
  fsl_size_t n;
  assert(f);
  if(!zName || !*zName) return FSL_RC_MISUSE;
  else if(!fsl_needs_ckout(f)) return FSL_RC_NOT_A_CKOUT;
  b = fsl_cx_scratchpad(f);
  bufRel = fsl_cx_scratchpad(f);
#if 1
  rc = fsl_ckout_filename_check(f, relativeToCwd, zName, bufRel);
  if(rc) goto end;
  zName = fsl_buffer_cstr2( bufRel, &n );
#else
  if(!fsl_is_simple_pathname(zName, 1)){
    rc = fsl_ckout_filename_check(f, relativeToCwd, zName, bufRel);
    if(rc) goto end;
    zName = fsl_buffer_cstr2( bufRel, &n );
    /* MARKER(("bufRel=%s\n",zName)); */
  }else{
    n = fsl_strlen(zName);
  }
#endif
  assert(n>0 &&
         "Will fail if fsl_ckout_filename_check() changes "
         "to return nothing if zName==checkout root");
  if(!n
     /* i don't like the "." resp "./" result when zName==checkout root */
     || (1==n && '.'==bufRel->mem[0])
     || (2==n && '.'==bufRel->mem[0] && '/'==bufRel->mem[1])){
    rc = fsl_buffer_appendf(b, "%s%s", f->ckout.dir,
                            (2==n) ? "/" : "");
  }else{
    rc = fsl_buffer_appendf(b, "%s%s", f->ckout.dir, zName);
  }
  if(!rc){
    rc = fsl_stat( fsl_buffer_cstr(b), tgt, false );
    if(rc){
      fsl_cx_err_set(f, rc, "Error %s from fsl_stat(\"%b\")",
                     fsl_rc_cstr(rc), b);
    }else if(nameOut){
      rc = fullPath
        ? fsl_buffer_append(nameOut, b->mem, b->used)
        : fsl_buffer_append(nameOut, zName, n);
    }
  }
  end:
  fsl_cx_scratchpad_yield(f, b);
  fsl_cx_scratchpad_yield(f, bufRel);
  return rc;
}

int fsl_cx_stat(fsl_cx * f, bool relativeToCwd, char const * zName,
                fsl_fstat * tgt){
  return fsl_cx_stat2(f, relativeToCwd, zName, tgt, NULL, 0);
}


void fsl_cx_case_sensitive_set(fsl_cx * f, bool caseSensitive){
  f->cache.caseInsensitive = caseSensitive;
}

bool fsl_cx_is_case_sensitive(fsl_cx const * f){
  return !f->cache.caseInsensitive;
}

char const * fsl_cx_filename_collation(fsl_cx const * f){
  return f->cache.caseInsensitive ? "COLLATE nocase" : "";
}


void fsl_cx_content_buffer_yield(fsl_cx * f){
  enum { MaxSize = 1024 * 1024 * 2 };
  assert(f);
  if(f->fileContent.capacity>MaxSize){
    fsl_buffer_resize(&f->fileContent, MaxSize);
    assert(f->fileContent.capacity<=MaxSize+1);
  }
  fsl_buffer_reuse(&f->fileContent);
}

fsl_error const * fsl_cx_err_get_e(fsl_cx const * f){
  return f ? &f->error : NULL;
}

int fsl_cx_close_dbs( fsl_cx * f ){
  if(fsl_cx_transaction_level(f)){
    return fsl_cx_err_set(f, FSL_RC_MISUSE,
                          "Cannot close the databases when a "
                          "transaction is pending.");
  }
  int rc = 0, rc1;
  rc1 = fsl_ckout_close(f);
  if(rc1) rc = rc1;
  rc1 = fsl_repo_close(f);
  if(rc1) rc = rc1;
  rc1 = fsl_config_close(f);
  if(rc1) rc = rc1;
  assert(!f->repo.db.dbh);
  assert(!f->ckout.db.dbh);
  assert(!f->config.db.dbh);
  return rc;
}

char const * fsl_cx_glob_matches( fsl_cx * f, int gtype,
                                  char const * str ){
  int i, count = 0;
  char const * rv = NULL;
  fsl_list const * lists[] = {0,0,0};
  if(!f || !str || !*str) return NULL;
  if(gtype & FSL_GLOBS_IGNORE) lists[count++] = &f->cache.globs.ignore;
  if(gtype & FSL_GLOBS_CRNL) lists[count++] = &f->cache.globs.crnl;
  /*CRNL/BINARY together makes little sense, but why strictly prohibit
    it?*/
  if(gtype & FSL_GLOBS_BINARY) lists[count++] = &f->cache.globs.binary;
  for( i = 0; i < count; ++i ){
    if( (rv = fsl_glob_list_matches( lists[i], str )) ) break;
  }
  return rv;
}

int fsl_output_f_fsl_cx(void * state, void const * src, fsl_size_t n ){
  return (state && src && n)
    ? fsl_output((fsl_cx*)state, src, n)
    : (n ? FSL_RC_MISUSE : 0);
}

int fsl_cx_hash_buffer( fsl_cx const * f, bool useAlternate,
                        fsl_buffer const * pIn, fsl_buffer * pOut){
  /* fossil(1) counterpart: hname_hash() */
  if(useAlternate){
    switch(f->cxConfig.hashPolicy){
      case FSL_HPOLICY_AUTO:
      case FSL_HPOLICY_SHA1:
        return fsl_sha3sum_buffer(pIn, pOut);
      case FSL_HPOLICY_SHA3:
        return fsl_sha1sum_buffer(pIn, pOut);
      default: return FSL_RC_UNSUPPORTED;
    }
  }else{
    switch(f->cxConfig.hashPolicy){
      case FSL_HPOLICY_SHA1:
      case FSL_HPOLICY_AUTO:
        return fsl_sha1sum_buffer(pIn, pOut);
      case FSL_HPOLICY_SHA3:
      case FSL_HPOLICY_SHA3_ONLY:
      case FSL_HPOLICY_SHUN_SHA1:
        return fsl_sha3sum_buffer(pIn, pOut);
    }
  }
  assert(!"not reached");
  return FSL_RC_RANGE;
}

int fsl_cx_hash_filename( fsl_cx * f, bool useAlternate,
                          const char * zFilename, fsl_buffer * pOut){
  /* FIXME: reimplement this to stream the content in bite-sized
     chunks. That requires duplicating most of fsl_buffer_fill_from()
     and fsl_cx_hash_buffer(). */
  fsl_buffer * content = &f->fileContent;
  int rc;
  assert(!content->used && "Internal recursive misuse of fsl_cx::fileContent");
  fsl_buffer_reuse(content);
  rc = fsl_buffer_fill_from_filename(content, zFilename);
  if(!rc){
    rc = fsl_cx_hash_buffer(f, useAlternate, content, pOut);
  }
  fsl_buffer_reuse(content);
  return rc;
}

char const * fsl_hash_policy_name(fsl_hashpolicy_e p){
  switch(p){
    case FSL_HPOLICY_SHUN_SHA1: return "shun-sha1";
    case FSL_HPOLICY_SHA3: return "sha3";
    case FSL_HPOLICY_SHA3_ONLY: return "sha3-only";
    case FSL_HPOLICY_SHA1: return "sha1";
    case FSL_HPOLICY_AUTO: return "auto";
    default: return NULL;
  }
}

fsl_hashpolicy_e fsl_cx_hash_policy_set(fsl_cx *f, fsl_hashpolicy_e p){
  fsl_hashpolicy_e const old = f->cxConfig.hashPolicy;
  fsl_db * const dbR = fsl_cx_db_repo(f);
  if(dbR){
    /* Write it regardless of whether it's the same as the old policy
       so that we're sure the db knows the policy. */
    if(FSL_HPOLICY_AUTO==p &&
       fsl_db_exists(dbR,"SELECT 1 FROM blob WHERE length(uuid)>40")){
      p = FSL_HPOLICY_SHA3;
    }
    fsl_config_set_int32(f, FSL_CONFDB_REPO, "hash-policy", p);
  }
  f->cxConfig.hashPolicy = p;
  return old;
}

fsl_hashpolicy_e fsl_cx_hash_policy_get(fsl_cx const*f){
  return f->cxConfig.hashPolicy;
}

int fsl_cx_transaction_level(fsl_cx * f){
  return f->dbMain
    ? fsl_db_transaction_level(f->dbMain)
    : 0;
}

int fsl_cx_transaction_begin(fsl_cx * f){
  return fsl_db_transaction_begin(f->dbMain);
}

int fsl_cx_transaction_end(fsl_cx * f, bool doRollback){
  return fsl_db_transaction_end(f->dbMain, doRollback);
}

void fsl_cx_confirmer(fsl_cx * f,
                      fsl_confirmer const * newConfirmer,
                      fsl_confirmer * prevConfirmer){
  if(prevConfirmer) *prevConfirmer = f->confirmer;
  f->confirmer = newConfirmer ? *newConfirmer : fsl_confirmer_empty;
}

void fsl_cx_confirmer_get(fsl_cx const * f, fsl_confirmer * dest){
  *dest = f->confirmer;
}

int fsl_cx_confirm(fsl_cx *f, fsl_confirm_detail const * detail,
                   fsl_confirm_response *outAnswer){
  if(f->confirmer.callback){
    return f->confirmer.callback(detail, outAnswer,
                                 f->confirmer.callbackState);
  }
  /* Default answers... */
  switch(detail->eventId){
    case FSL_CEVENT_OVERWRITE_MOD_FILE:
    case FSL_CEVENT_OVERWRITE_UNMGD_FILE:
      outAnswer->response =  FSL_CRESPONSE_NEVER;
      break;
    case FSL_CEVENT_RM_MOD_UNMGD_FILE:
      outAnswer->response = FSL_CRESPONSE_NEVER;
      break;
    case FSL_CEVENT_MULTIPLE_VERSIONS:
      outAnswer->response = FSL_CRESPONSE_CANCEL;
      break;
    default:
      assert(!"Unhandled fsl_confirm_event_e value");
      fsl_fatal(FSL_RC_UNSUPPORTED,
                "Unhandled fsl_confirm_event_e value: %d",
                detail->eventId)/*does not return*/;
  }
  return 0;
}

int fsl_cx_update_seen_delta_mf(fsl_cx *f){
  int rc = 0;
  fsl_db * const d = fsl_cx_db_repo(f);
  if(d && f->cache.seenDeltaManifest <= 0){
    f->cache.seenDeltaManifest = 1;
    rc = fsl_config_set_bool(f, FSL_CONFDB_REPO,
                             "seen-delta-manifest", 1);
  }
  return rc;
}

int fsl_reserved_fn_check(fsl_cx *f, const char *zPath,
                          fsl_int_t nPath, bool relativeToCwd){
  static const int errRc = FSL_RC_RANGE;
  int rc = 0;
  char const * z1 = 0;
  if(nPath<0) nPath = (fsl_int_t)fsl_strlen(zPath);
  if(fsl_is_reserved_fn(zPath, nPath)){
    return fsl_cx_err_set(f, errRc,
                        "Filename is reserved, not legal "
                        "for adding to a repository: %.*s",
                        (int)nPath, zPath);
  }
  if(!(f->flags & FSL_CX_F_ALLOW_WINDOWS_RESERVED_NAMES)
     && fsl_is_reserved_fn_windows(zPath, nPath)){
    return fsl_cx_err_set(f, errRc,
                          "Filename is a Windows reserved name: %.*s",
                          (int)nPath, zPath);
  }
  if((z1 = fsl_cx_db_file_for_role(f, FSL_DBROLE_REPO, NULL))){
    fsl_buffer * c1 = fsl_cx_scratchpad(f);
    fsl_buffer * c2 = fsl_cx_scratchpad(f);
    rc = fsl_file_canonical_name2(relativeToCwd ? NULL : f->ckout.dir/*NULL is okay*/,
                                  z1, c1, false);
    if(!rc) rc = fsl_file_canonical_name2(relativeToCwd ? NULL : f->ckout.dir,
                                          zPath, c2, false);
    //MARKER(("\nzPath=%s\nc1=%s\nc2=%s\n", zPath,
    //fsl_buffer_cstr(c1), fsl_buffer_cstr(c2)));
    if(!rc && c1->used == c2->used &&
       0==fsl_stricmp(fsl_buffer_cstr(c1), fsl_buffer_cstr(c2))){
      rc = fsl_cx_err_set(f, errRc, "File is the repository database: %.*s",
                          (int)nPath, zPath);
    }
    fsl_cx_scratchpad_yield(f, c1);
    fsl_cx_scratchpad_yield(f, c2);
    if(rc) return rc;
  }
  assert(!rc);
  while(f->ckout.dir || relativeToCwd){
    int manifestSetting = 0;
    fsl_ckout_manifest_setting(f, &manifestSetting);
    if(!manifestSetting) break;
    typedef struct {
      short flag;
      char const * fn;
    } MSetting;
    const MSetting M[] = {
    {FSL_MANIFEST_MAIN, "manifest"},
    {FSL_MANIFEST_UUID, "manifest.uuid"},
    {FSL_MANIFEST_TAGS, "manifest.tags"},
    {0,0}
    };
    fsl_buffer * c1 = fsl_cx_scratchpad(f);
    if(f->ckout.dir){
      rc = fsl_ckout_filename_check(f, relativeToCwd, zPath, c1);
    }else{
      rc = fsl_file_canonical_name2("", zPath, c1, false);
    }
    if(rc) goto yield;
    char const * const z = fsl_buffer_cstr(c1);
    //MARKER(("Checking file against manifest setting 0x%03x: %s\n",
    //manifestSetting, z));
    for( MSetting const * m = &M[0]; m->fn; ++m ){
      if((m->flag & manifestSetting)
         && 0==fsl_strcmp(z, m->fn)){
        rc = fsl_cx_err_set(f, errRc,
                            "Filename is reserved due to the "
                            "'manifest' setting: %s",
                            m->fn);
        break;
      }
    }
    yield:
    fsl_cx_scratchpad_yield(f, c1);
    break;
  }
  return rc;
}

fsl_buffer * fsl_cx_scratchpad(fsl_cx *f){
  fsl_buffer * rc = 0;
  int i = (f->scratchpads.next<FSL_CX_NSCRATCH)
    ? f->scratchpads.next : 0;
  for(; i < FSL_CX_NSCRATCH; ++i){
    if(!f->scratchpads.used[i]){
      rc = &f->scratchpads.buf[i];
      f->scratchpads.used[i] = true;
      ++f->scratchpads.next;
      //MARKER(("Doling out scratchpad[%d] w/ capacity=%d next=%d\n",
      //        i, (int)rc->capacity, f->scratchpads.next));
      break;
    }
  }
  if(!rc){
    assert(!"Fatal fsl_cx::scratchpads misuse.");
    fsl_fatal(FSL_RC_MISUSE,
              "Fatal internal fsl_cx::scratchpads misuse: "
              "too many unyielded buffer requests.");
  }else if(0!=rc->used){
    assert(!"Fatal fsl_cx::scratchpads misuse.");
    fsl_fatal(FSL_RC_MISUSE,
              "Fatal internal fsl_cx::scratchpads misuse: "
              "used buffer after yielding it.");
  }
  return rc;
}

void fsl_cx_scratchpad_yield(fsl_cx *f, fsl_buffer * b){
  int i;
  assert(b);
  for(i = 0; i < FSL_CX_NSCRATCH; ++i){
    if(b == &f->scratchpads.buf[i]){
      assert(f->scratchpads.next != i);
      assert(f->scratchpads.used[i] && "Scratchpad misuse.");
      f->scratchpads.used[i] = false;
      fsl_buffer_reuse(b);
      if(f->scratchpads.next>i) f->scratchpads.next = i;
      //MARKER(("Yielded scratchpad[%d] w/ capacity=%d, next=%d\n",
      //        i, (int)b->capacity, f->scratchpads.next));
      return;
    }
  }
  fsl_fatal(FSL_RC_MISUSE,
            "Fatal internal fsl_cx::scratchpads misuse: "
            "passed a non-scratchpad buffer.");
}


/** @internal

   Don't use this. Use fsl_cx_rm_empty_dirs() instead.

   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 first argument must be the canonicalized absolute path to the
   checkout root. The second is the length of coRoot - if it's
   negative then fsl_strlen() is used to calculate it. The third 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 or does not
   have coRoot as its initial prefix.

   There are any number of valid reasons removal of a directory might
   fail, and this routine stops at the first one which does.
*/
static unsigned fsl_rm_empty_dirs(char const *coRoot, fsl_int_t rootLen,
                                  fsl_buffer * tgtDir){
  if(rootLen<0) rootLen = fsl_strlen(coRoot);
  char const * zAbs = fsl_buffer_cstr(tgtDir);
  char const * zCoDirPart = zAbs + rootLen;
  char * zEnd = fsl_buffer_str(tgtDir) + tgtDir->used - 1;
  unsigned rc = 0;
  assert(coRoot);
  if(0!=memcmp(coRoot, zAbs, (size_t)rootLen)){
    assert(!"Misuse of fsl_rm_empty_dirs()");
    return 0;
  }
  if(fsl_rmdir(zAbs)) return rc;
  ++rc;
  /** Now walk up each dir in the path and try to remove each,
      stopping when removal of one fails or we reach coRoot. */
  while(zEnd>zCoDirPart){
    for( ; zEnd>zCoDirPart && '/'!=*zEnd; --zEnd ){}
    if(zEnd==zCoDirPart) break;
    else if('/'==*zEnd){
      *zEnd = 0;
      assert(zEnd>zCoDirPart);
      if(fsl_rmdir(zAbs)) break;
      ++rc;
    }
  }
  return rc;
}

unsigned int fsl_ckout_rm_empty_dirs(fsl_cx * f, fsl_buffer * tgtDir){
  int rc = f->ckout.dir ? 0 : FSL_RC_NOT_A_CKOUT;
  if(!rc){
    rc = fsl_rm_empty_dirs(f->ckout.dir, f->ckout.dirLen, tgtDir);
  }
  return rc;
}

int fsl_ckout_rm_empty_dirs_for_file(fsl_cx * f, char const *zAbsPath){
  if(!fsl_is_rooted_in_ckout(f, zAbsPath)){
    assert(!"Internal API misuse!");
    return FSL_RC_MISUSE;
  }else{
    fsl_buffer * const p = fsl_cx_scratchpad(f);
    fsl_int_t const nAbs = (fsl_int_t)fsl_strlen(zAbsPath);
    int const rc = fsl_file_dirpart(zAbsPath, nAbs, p, false);
    if(!rc) fsl_rm_empty_dirs(f->ckout.dir, f->ckout.dirLen, p);
    fsl_cx_scratchpad_yield(f,p);
    return rc;
  }
}

bool fsl_repo_forbids_delta_manifests(fsl_cx * f){
  return fsl_config_get_bool(f, FSL_CONFDB_REPO, false,
                             "forbid-delta-manifests");
}

int fsl_ckout_fingerprint_check(fsl_cx * f){
  fsl_db * const db = fsl_needs_ckout(f);
  if(!db) return FSL_RC_NOT_A_CKOUT;
  int rc = 0;
  char const * zCkout = 0;
  char * zRepo = 0;
  fsl_id_t rcvCkout = 0;
  fsl_buffer * const buf = fsl_cx_scratchpad(f);
  rc = fsl_config_get_buffer(f, FSL_CONFDB_CKOUT, "fingerprint", buf);
  if(FSL_RC_NOT_FOUND==rc){
    /* Older checkout with no fingerprint. Assume it's okay. */
    rc = 0;
    goto end;
  }else if(rc){
    goto end;
  }
  zCkout = fsl_buffer_cstr(buf);
#if 0
  /* Inject a bogus byte for testing purposes */
  buf->mem[6] = 'x';
#endif
  rcvCkout = (fsl_id_t)atoi(zCkout);
  rc = fsl_repo_fingerprint_search(f, rcvCkout, &zRepo);
  switch(rc){
    case FSL_RC_NOT_FOUND: goto mismatch;
    case 0:
      assert(zRepo);
      if(fsl_strcmp(zRepo,zCkout)) goto mismatch;
      break;
    default:
      break;
  }
  end:
  fsl_cx_scratchpad_yield(f, buf);
  fsl_free(zRepo);
  return rc;
  mismatch:
  rc = fsl_cx_err_set(f, FSL_RC_REPO_MISMATCH,
                      "Mismatch found between repo/checkout "
                      "fingerprints.");
  goto end;
}

#if 0
struct tm * fsl_cx_localtime( fsl_cx const * f, const time_t * clock ){
  if(!clock) return NULL;
  else if(!f) return localtime(clock);
  else return (f->flags & FSL_CX_F_LOCALTIME_GMT)
         ? gmtime(clock)
         : localtime(clock)
         ;
}

struct tm * fsl_localtime( const time_t * clock ){
  return fsl_cx_localtime(NULL, clock);
}

time_t fsl_cx_time_adj(fsl_cx const * f, time_t clock){
  struct tm * tm = fsl_cx_localtime(f, &clock);
  return tm ? mktime(tm) : 0;
}
#endif

#undef MARKER
#undef FSL_CX_NSCRATCH
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<














































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/db.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  Copyright 2013-2021 Stephan Beal (https://wanderinghorse.net).


  
  This program is free software; you can redistribute it and/or
  modify it under the terms of the Simplified BSD License (also
  known as the "2-Clause License" or "FreeBSD License".)
  
  This program is distributed in the hope that it will be useful,
  but without any warranty; without even the implied warranty of
  merchantability or fitness for a particular purpose.
  
  *****************************************************************************
  This file contains the fsl_db_xxx() and fsl_stmt_xxx() parts of the
  API.
  
  Maintenance reminders:
  
  When returning dynamically allocated memory to the client, it needs
  to come from fsl_malloc(), as opposed to sqlite3_malloc(), so that
  it is legal to pass to fsl_free().
*/
#include "fossil-scm/fossil.h"
#include "fossil-scm/fossil-internal.h"
#include <assert.h>
#include <stddef.h> /* NULL on linux */
#include <time.h> /* time() and friends */

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

#if 0
/**
    fsl_list_visitor_f() impl which requires that obj be NULL or
    a (fsl_st*), which it passed to fsl_stmt_finalize().
 */
static int fsl_list_v_fsl_stmt_finalize(void * obj, void * visitorState ){
  if(obj) fsl_stmt_finalize( (fsl_stmt*)obj );
  return 0;
}
#endif

void fsl_db_clear_strings(fsl_db * db, bool alsoErrorState ){
  fsl_free(db->filename);
  db->filename = NULL;
  fsl_free(db->name);
  db->name = NULL;
  if(alsoErrorState) fsl_error_clear(&db->error);
}

int fsl_db_err_get( fsl_db const * db, char const ** msg, fsl_size_t * len ){
  return db
    ? fsl_error_get(&db->error, msg, len)
    : FSL_RC_MISUSE;
}

fsl_db * fsl_stmt_db( fsl_stmt * stmt ){
  return stmt ? stmt->db : NULL;
}

/**
    Resets db->error state based on the given code and the current
    error string from the db driver. Returns FSL_RC_DB on success,
    some other non-0 value on error (most likely FSL_RC_OOM while
    allocating the error string - that's the only other error case as
    long as db is opened). Results are undefined if !db or db is not
    opened.
 */
static int fsl_err_from_db( fsl_db * db, int dbCode ){
  assert(db && db->dbh);
  db->error.msg.used =0 ;
  return fsl_error_set(&db->error, FSL_RC_DB,
                       "Db error #%d: %s",
                       dbCode, sqlite3_errmsg(db->dbh));
}

char const * fsl_stmt_sql( fsl_stmt * stmt, fsl_size_t * len ){
  return stmt
    ? fsl_buffer_cstr2(&stmt->sql, len)
    : NULL;
}

char const * fsl_db_filename(fsl_db const * db, fsl_size_t * len){
  if(!db) return NULL;
  if(len && db->filename) *len = fsl_strlen(db->filename);
  return db->filename;
}

fsl_id_t fsl_db_last_insert_id(fsl_db *db){
  return (db && db->dbh)
    ? (fsl_id_t)sqlite3_last_insert_rowid(db->dbh)
    : -1;
}

/**
    Cleans up db->beforeCommit and its contents.
 */
static void fsl_db_cleanup_beforeCommit( fsl_db * db ){
  fsl_list_visit( &db->beforeCommit, -1, fsl_list_v_fsl_free, NULL );
  fsl_list_reserve(&db->beforeCommit, 0);
}

fsl_size_t fsl_db_stmt_cache_clear(fsl_db * db){
  fsl_size_t rc = 0;
  if(db && db->cacheHead){
    fsl_stmt * st;
    fsl_stmt * next = 0;
    for( st = db->cacheHead; st; st = next, ++rc ){
      next = st->next;
      st->next = 0;
      fsl_stmt_finalize( st );
    }
    db->cacheHead = 0;
  }
  return rc;
}

int fsl_db_close( fsl_db * db ){
  if(!db) return FSL_RC_MISUSE;
  else{
    void const * allocStamp = db->allocStamp;
    fsl_cx * f = db->f;
    fsl_db_stmt_cache_clear(db);
    if(db->f && db->f->dbMain==db){
      /*
         Horrible, horrible dependency, and only necessary if the
         fsl_cx API gets sloppy or repo/checkout/config DBs are
         otherwised closed improperly (i.e. not via the fsl_cx API).
      */
      assert(0 != db->role);
      f->dbMain = NULL;
    }
    while(db->beginCount>0){
      fsl_db_transaction_end(db, 1);
    }
    if(0!=db->openStatementCount){
      MARKER(("WARNING: %d open statement(s) left on db [%s].\n",
              (int)db->openStatementCount, db->filename));
    }
    if(db->dbh){
      sqlite3_close(db->dbh);
      /* ignoring results in the style of "destructors may not
         throw". */
    }
    fsl_db_clear_strings(db, 1);
    fsl_db_cleanup_beforeCommit(db);
    *db = fsl_db_empty;
    if(&fsl_db_empty == allocStamp){
      fsl_free( db );
    }else{
      db->allocStamp = allocStamp;
      db->f = f;
    }
    return 0;
  }
}

void fsl_db_err_reset( fsl_db * db ){
  if(db && (db->error.code||db->error.msg.used)){
    fsl_error_reset(&db->error);
  }
}


int fsl_db_attach(fsl_db * db, const char *zDbName, const char *zLabel){
  return (db && db->dbh && zDbName && *zDbName && zLabel && *zLabel)
    ? fsl_db_exec(db, "ATTACH DATABASE %Q AS %s", zDbName, zLabel)
    : FSL_RC_MISUSE;
}
int fsl_db_detach(fsl_db * db, const char *zLabel){
  return (db && db->dbh && zLabel && *zLabel)
    ? fsl_db_exec(db, "DETACH DATABASE %s /*%s()*/", zLabel, __func__)
    : FSL_RC_MISUSE;
}

char const * fsl_db_name(fsl_db const * db){
  return db ? db->name : NULL;
}

/**
    Returns the db name for the given role.
 */
const char * fsl_db_role_label(fsl_dbrole_e r){
  switch(r){
    case FSL_DBROLE_CONFIG:
      return "cfg";
    case FSL_DBROLE_REPO:
      return "repo";
    case FSL_DBROLE_CKOUT:
      return "ckout";
    case FSL_DBROLE_MAIN:
      return "main";
    case FSL_DBROLE_NONE:
    default:
      assert(!"cannot happen/not legal");
      return NULL;
  }
}

char * fsl_db_julian_to_iso8601( fsl_db * db, double j,
                                 char msPrecision,
                                 char localTime){
  char * s = NULL;
  fsl_stmt * st = NULL;
  if(db && db->dbh && (j>=0.0)){
    char const * sql;
    if(msPrecision){
      sql = localTime
        ? "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',?, 'localtime')"
        : "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',?)";
    }else{
      sql = localTime
        ? "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%S',?, 'localtime')"
        : "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%S',?)";
    }
    fsl_db_prepare_cached(db, &st, sql);
    if(st){
      fsl_stmt_bind_double( st, 1, j );
      if( FSL_RC_STEP_ROW==fsl_stmt_step(st) ){
        s = fsl_strdup(fsl_stmt_g_text(st, 0, NULL));
      }
      fsl_stmt_cached_yield(st);
    }
  }
  return s;
}

char * fsl_db_unix_to_iso8601( fsl_db * db, fsl_time_t t, char localTime ){
  char * s = NULL;
  fsl_stmt st = fsl_stmt_empty;
  if(db && db->dbh && (t>=0)){
    char const * sql = localTime
      ? "SELECT datetime(?, 'unixepoch', 'localtime')/*%s()*/"
      : "SELECT datetime(?, 'unixepoch')/*%s()*/"
      ;
    int const rc = fsl_db_prepare(db, &st, sql,__func__);
    if(!rc){
      fsl_stmt_bind_int64( &st, 1, t );
      if( FSL_RC_STEP_ROW==fsl_stmt_step(&st) ){
        fsl_size_t n = 0;
        char const * v = fsl_stmt_g_text(&st, 0, &n);
        s = (v&&n) ? fsl_strndup(v, (fsl_int_t)n) : NULL;
      }
      fsl_stmt_finalize(&st);
    }
  }
  return s;
}

enum fsl_stmt_flags_e {
/**
    fsl_stmt::flags bit indicating that fsl_db_preparev_cache() has
    doled out this statement, effectively locking it until
    fsl_stmt_cached_yield() is called to release it.
 */
FSL_STMT_F_CACHE_HELD = 0x01,

/**
   Propagates our intent to "statically" prepare a given statement
   through various internal API calls.
*/
FSL_STMT_F_PREP_CACHE = 0x10
};

int fsl_db_preparev( fsl_db *db, fsl_stmt * tgt, char const * sql, va_list args ){
  if(!db || !tgt || !sql) return FSL_RC_MISUSE;
  else if(!db->dbh){
    return fsl_error_set(&db->error, FSL_RC_NOT_FOUND, "Db is not opened.");
  }else if(!*sql){
    return fsl_error_set(&db->error, FSL_RC_RANGE, "SQL is empty.");
  }else if(tgt->stmt){
    return fsl_error_set(&db->error, FSL_RC_ALREADY_EXISTS,
                         "Error: attempt to re-prepare "
                         "active statement.");
  }
  else{
    int rc;
    fsl_buffer buf = fsl_buffer_empty;
    fsl_stmt_t * liteStmt = NULL;
    rc = fsl_buffer_appendfv( &buf, sql, args );
    if(!rc){
#if 0
      /* Arguably improves readability of some queries.
         And breaks some caching uses. */
      fsl_simplify_sql_buffer(&buf);
#endif
      sql = fsl_buffer_cstr(&buf);
      if(!sql || !*sql){
        rc = fsl_error_set(&db->error, FSL_RC_RANGE,
                           "Input SQL is empty.");
      }else{
        /*
          Achtung: if sql==NULL here, or evaluates to a no-op
          (e.g. only comments or spaces), prepare_v2 succeeds but has
          a NULL liteStmt, which is why we handle the empty-SQL case
          specially. We don't want that specific behaviour leaking up
          through the API. Though doing so would arguably more correct
          in a generic API, for this particular API we have no reason
          to be able to handle empty SQL. Were we do let through
          through we'd have to add a flag to fsl_stmt to tell us
          whether it's really prepared or not, since checking of
          st->stmt would no longer be useful.
        */
        rc = sqlite3_prepare_v3(db->dbh, sql, (int)buf.used,
                                (FSL_STMT_F_PREP_CACHE & tgt->flags)
                                ? SQLITE_PREPARE_PERSISTENT
                                : 0,
                                &liteStmt, NULL);
        if(rc){
          rc = fsl_error_set(&db->error, FSL_RC_DB,
                             "Db statement preparation failed. "
                             "Error #%d: %s. SQL: %.*s",
                             rc, sqlite3_errmsg(db->dbh),
                             (int)buf.used, (char const *)buf.mem);
        }else if(!liteStmt){
          /* SQL was empty. In sqlite this is allowed, but this API will
             disallow this because it leads to headaches downstream.
          */
          rc = fsl_error_set(&db->error, FSL_RC_RANGE,
                             "Input SQL is empty.");
        }
      }
    }
    if(!rc){
      assert(liteStmt);
      ++db->openStatementCount;
      tgt->stmt = liteStmt;
      tgt->db = db;
      tgt->sql = buf /*transfer ownership*/;
      tgt->colCount = sqlite3_column_count(tgt->stmt);
      tgt->paramCount = sqlite3_bind_parameter_count(tgt->stmt);
    }else{
      assert(!liteStmt);
      fsl_buffer_clear(&buf);
      /* TODO: consider _copying_ the error state to db->f->error if
         db->f is not NULL. OTOH, we don't _always_ want to propagate
         a db error to the parent fossil context, and doing so here
         could lead to a "stale" error laying around downstream and
         getting evaluated later on (incorrectly) in a success
         context.
      */
    }
    return rc;
  }
}

int fsl_db_prepare( fsl_db *db, fsl_stmt * tgt, char const * sql, ... ){
  int rc;
  va_list args;
  va_start(args,sql);
  rc = fsl_db_preparev( db, tgt, sql, args );
  va_end(args);
  return rc;
}

int fsl_db_preparev_cached( fsl_db * db, fsl_stmt ** rv,
                            char const * sql, va_list args ){
  int rc = 0;
  fsl_buffer buf = fsl_buffer_empty;
  fsl_stmt * st = NULL;
  fsl_stmt * cs = NULL;
  if(!db || !rv || !sql) return FSL_RC_MISUSE;
  else if(!*sql) return FSL_RC_RANGE;
  rc = fsl_buffer_appendfv( &buf, sql, args );
  if(rc) goto end;
  for( cs = db->cacheHead; cs; cs = cs->next ){
    if(0==fsl_buffer_compare(&buf, &cs->sql)){
      if(cs->flags & FSL_STMT_F_CACHE_HELD){
        rc = fsl_error_set(&db->error, FSL_RC_ACCESS,
                           "Cached statement is already in use. "
                           "Do not use cached statements if recursion "
                           "involving the statement is possible, and use "
                           "fsl_stmt_cached_yield() to release them "
                           "for further (re)use. SQL: %b",
                           &cs->sql);
        goto end;
      }
      cs->flags |= FSL_STMT_F_CACHE_HELD;
      *rv = cs;
      goto end;
    }
  }
  st = fsl_stmt_malloc();
  if(!st){
    rc = FSL_RC_OOM;
    goto end;
  }
  st->flags |= FSL_STMT_F_PREP_CACHE;
  rc = fsl_db_prepare( db, st, "%b", &buf );
  if(rc){
    fsl_free(st);
    st = 0;
  }else{
    st->next = db->cacheHead;
    db->cacheHead = st;
    st->flags = FSL_STMT_F_CACHE_HELD;
    *rv = st;
  }
  end:
  fsl_buffer_clear(&buf);
  return rc;
}

int fsl_db_prepare_cached( fsl_db * db, fsl_stmt ** st, char const * sql, ... ){
  int rc;
  va_list args;
  va_start(args,sql);
  rc = fsl_db_preparev_cached( db, st, sql, args );
  va_end(args);
  return rc;
}

int fsl_stmt_cached_yield( fsl_stmt * st ){
  if(!st || !st->db || !st->stmt) return FSL_RC_MISUSE;
  else if(!(st->flags & FSL_STMT_F_CACHE_HELD)) {
    return fsl_error_set(&st->db->error, FSL_RC_MISUSE,
                         "fsl_stmt_cached_yield() was passed a "
                         "statement which is not marked as cached. "
                         "SQL: %b",
                         &st->sql);
  }else{
    fsl_stmt_reset(st);
    st->flags &= ~FSL_STMT_F_CACHE_HELD;
    return 0;
  }
}

int fsl_db_before_commitv( fsl_db * db, char const * sql,
                           va_list args ){
  int rc = 0;
  char * cp = NULL;
  if(!db || !sql) return FSL_RC_MISUSE;
  else if(!*sql) return FSL_RC_RANGE;
  cp = fsl_mprintfv(sql, args);
  if(cp){
    rc = fsl_list_append(&db->beforeCommit, cp);
    if(rc) fsl_free(cp);
  }else{
    rc = FSL_RC_OOM;
  }
  return rc;
}

int fsl_db_before_commit( fsl_db *db, char const * sql, ... ){
  int rc;
  va_list args;
  va_start(args,sql);
  rc = fsl_db_before_commitv( db, sql, args );
  va_end(args);
  return rc;
}

int fsl_stmt_finalize( fsl_stmt * stmt ){
  if(!stmt) return FSL_RC_MISUSE;
  else{
    void const * allocStamp = stmt->allocStamp;
    fsl_db * db = stmt->db;
    if(db){
      if(stmt->sql.mem){
        /* ^^^ b/c that buffer is set at the same time
           that openStatementCount is incremented.
        */
        --stmt->db->openStatementCount;
      }
      if(allocStamp && db->cacheHead){
        /* It _might_ be cached - let's remove it.
           We use allocStamp as a check here only
           because most statements allocated on the
           heap currently come from caching.
        */
        fsl_stmt * s;
        fsl_stmt * prev = 0;
        for( s = db->cacheHead; s; prev = s, s = s->next ){
          if(s == stmt){
            if(prev){
              assert(prev->next == s);
              prev->next = s->next;
            }else{
              assert(s == db->cacheHead);
              db->cacheHead = s->next;
            }
            s->next = 0;
            break;
          }
        }
      }
    }
    fsl_buffer_clear(&stmt->sql);
    if(stmt->stmt){
      sqlite3_finalize( stmt->stmt );
    }
    *stmt = fsl_stmt_empty;
    if(&fsl_stmt_empty==allocStamp){
      fsl_free(stmt);
    }else{
      stmt->allocStamp = allocStamp;
    }
    return 0;
  }
}

int fsl_stmt_step( fsl_stmt * stmt ){
  if(!stmt || !stmt->stmt) return FSL_RC_MISUSE;
  else{
    int const rc = sqlite3_step(stmt->stmt);
    assert(stmt->db);
    switch( rc ){
      case SQLITE_ROW:
        ++stmt->rowCount;
        return FSL_RC_STEP_ROW;
      case SQLITE_DONE:
        return FSL_RC_STEP_DONE;
      default:
        fsl_error_set(&stmt->db->error, FSL_RC_STEP_ERROR,
                      "sqlite error #%d: %s",
                      rc, sqlite3_errmsg(stmt->db->dbh));
        return FSL_RC_STEP_ERROR;
    }
  }
}

int fsl_db_eachv( fsl_db * db, fsl_stmt_each_f callback,
                  void * callbackState, char const * sql, va_list args ){
  if(!db || !db->dbh || !callback || !sql) return FSL_RC_MISUSE;
  else if(!*sql) return FSL_RC_RANGE;
  else{
    fsl_stmt st = fsl_stmt_empty;
    int rc;
    rc = fsl_db_preparev( db, &st, sql, args );
    if(!rc){
      rc = fsl_stmt_each( &st, callback, callbackState );
      fsl_stmt_finalize( &st );
    }
    return rc;
  }
}

int fsl_db_each( fsl_db * db, fsl_stmt_each_f callback,
                 void * callbackState, char const * sql, ... ){
  int rc;
  va_list args;
  va_start(args,sql);
  rc = fsl_db_eachv( db, callback, callbackState, sql, args );
  va_end(args);
  return rc;
}

int fsl_stmt_each( fsl_stmt * stmt, fsl_stmt_each_f callback,
                   void * callbackState ){
  if(!stmt || !callback) return FSL_RC_MISUSE;
  else{
    int strc;
    int rc = 0;
    char doBreak = 0;
    while( !doBreak && (FSL_RC_STEP_ROW == (strc=fsl_stmt_step(stmt)))){
      rc = callback( stmt, callbackState );
      switch(rc){
        case 0: continue;
        case FSL_RC_BREAK:
          rc = 0;
          /* fall through */
        default:
          doBreak = 1;
          break;
      }
    }
    return rc
      ? rc
      : ((FSL_RC_STEP_ERROR==strc)
         ? FSL_RC_DB
         : 0);
  }
}

int fsl_stmt_reset2( fsl_stmt * stmt, bool resetRowCounter ){
  if(!stmt || !stmt->stmt || !stmt->db) return FSL_RC_MISUSE;
  else{
    int const rc = sqlite3_reset(stmt->stmt);
    if(resetRowCounter) stmt->rowCount = 0;
    assert(stmt->db);
    return rc
      ? fsl_err_from_db(stmt->db, rc)
      : 0;
  }
}

int fsl_stmt_reset( fsl_stmt * stmt ){
  return fsl_stmt_reset2(stmt, 0);
}

int fsl_stmt_col_count( fsl_stmt const * stmt ){
  return (!stmt || !stmt->stmt)
    ? -1
    : stmt->colCount
    ;
}

char const * fsl_stmt_col_name(fsl_stmt * stmt, int index){
  return (stmt && stmt->stmt && (index>=0 && index<stmt->colCount))
    ? sqlite3_column_name(stmt->stmt, index)
    : NULL;
}

int fsl_stmt_param_count( fsl_stmt const * stmt ){
  return (!stmt || !stmt->stmt)
    ? -1
    : stmt->paramCount;
}

int fsl_stmt_bind_fmtv( fsl_stmt * st, char const * fmt, va_list args ){
  int rc = 0, ndx;
  char const * pos = fmt;
  if(!fmt ||
     !(st && st->stmt && st->db && st->db->dbh)) return FSL_RC_MISUSE;
  else if(!*fmt) return FSL_RC_RANGE;
  for( ndx = 1; !rc && *pos; ++pos, ++ndx ){
    if(' '==*pos){
      --ndx;
      continue;
    }
    if(ndx > st->paramCount){
      rc = fsl_error_set(&st->db->error, FSL_RC_RANGE,
                         "Column index %d is out of bounds.", ndx);
      break;
    }
    switch(*pos){
      case '-':
        va_arg(args,void const *) /* skip arg */;
        rc = fsl_stmt_bind_null(st, ndx);
        break;
      case 'i':
        rc = fsl_stmt_bind_int32(st, ndx, va_arg(args,int32_t));
        break;
      case 'I':
        rc = fsl_stmt_bind_int64(st, ndx, va_arg(args,int64_t));
        break;
      case 'R':
        rc = fsl_stmt_bind_id(st, ndx, va_arg(args,fsl_id_t));
        break;
      case 'f':
        rc = fsl_stmt_bind_double(st, ndx, va_arg(args,double));
        break;
      case 's':{/* C-string as TEXT or NULL */
        char const * s = va_arg(args,char const *);
        rc = s
          ? fsl_stmt_bind_text(st, ndx, s, -1, false)
          : fsl_stmt_bind_null(st, ndx);
        break;
      }
      case 'S':{ /* C-string as BLOB or NULL */
        char const * s = va_arg(args,char const *);
        rc = s
          ? fsl_stmt_bind_blob(st, ndx, s, fsl_strlen(s), false)
          : fsl_stmt_bind_null(st, ndx);
        break;
      }
      case 'b':{ /* fsl_buffer as TEXT or NULL */
        fsl_buffer const * b = va_arg(args,fsl_buffer const *);
        rc = (b && b->mem)
          ? fsl_stmt_bind_text(st, ndx, (char const *)b->mem,
                               (fsl_int_t)b->used, false)
          : fsl_stmt_bind_null(st, ndx);
        break;
      }
      case 'B':{ /* fsl_buffer as BLOB or NULL */
        fsl_buffer const * b = va_arg(args,fsl_buffer const *);
        rc = (b && b->mem)
          ? fsl_stmt_bind_blob(st, ndx, b->mem, b->used, false)
          : fsl_stmt_bind_null(st, ndx);
        break;
      }
      default:
        rc = fsl_error_set(&st->db->error, FSL_RC_RANGE,
                           "Invalid format character: '%c'", *pos);
        break;
    }
  }
  return rc;
}

/**
   The elipsis counterpart of fsl_stmt_bind_fmtv().
*/
int fsl_stmt_bind_fmt( fsl_stmt * st, char const * fmt, ... ){
  int rc;
  va_list args;
  va_start(args,fmt);
  rc = fsl_stmt_bind_fmtv(st, fmt, args);
  va_end(args);
  return rc;
}

int fsl_stmt_bind_stepv( fsl_stmt * st, char const * fmt,
                         va_list args ){
  int rc;
  fsl_stmt_reset(st);
  rc = fsl_stmt_bind_fmtv(st, fmt, args);
  if(!rc){
    rc = fsl_stmt_step(st);
    switch(rc){
      case FSL_RC_STEP_DONE:
        rc = 0;
        fsl_stmt_reset(st);
        break;
      case FSL_RC_STEP_ROW:
        /* Don't reset() for ROW b/c that clears the column
           data! */
        break;
      default:
        rc = fsl_error_set(&st->db->error, rc,
                           "Error stepping statement.");
        break;
    }
  }
  return rc;
}

int fsl_stmt_bind_step( fsl_stmt * st, char const * fmt, ... ){
  int rc;
  va_list args;
  va_start(args,fmt);
  rc = fsl_stmt_bind_stepv(st, fmt, args);
  va_end(args);
  return rc;
}


#define BIND_PARAM_CHECK \
  if(!(stmt && stmt->stmt && stmt->db && stmt->db->dbh)) return FSL_RC_MISUSE; else
#define BIND_PARAM_CHECK2 BIND_PARAM_CHECK \
  if(ndx<1 || ndx>stmt->paramCount) return FSL_RC_RANGE; else
int fsl_stmt_bind_null( fsl_stmt * stmt, int ndx ){
  BIND_PARAM_CHECK2 {
    int const rc = sqlite3_bind_null( stmt->stmt, ndx );
    return rc ? fsl_err_from_db(stmt->db, rc) : 0;
  }
}

int fsl_stmt_bind_int32( fsl_stmt * stmt, int ndx, int32_t v ){
  BIND_PARAM_CHECK2 {
    int const rc = sqlite3_bind_int( stmt->stmt, ndx, (int)v );
    return rc ? fsl_err_from_db(stmt->db, rc) : 0;
  }
}

int fsl_stmt_bind_int64( fsl_stmt * stmt, int ndx, int64_t v ){
  BIND_PARAM_CHECK2 {
    int const rc = sqlite3_bind_int64( stmt->stmt, ndx, (sqlite3_int64)v );
    return rc ? fsl_err_from_db(stmt->db, rc) : 0;
  }
}

int fsl_stmt_bind_id( fsl_stmt * stmt, int ndx, fsl_id_t v ){
  BIND_PARAM_CHECK2 {
    int const rc = sqlite3_bind_int64( stmt->stmt, ndx, (sqlite3_int64)v );
    return rc ? fsl_err_from_db(stmt->db, rc) : 0;
  }
}

int fsl_stmt_bind_double( fsl_stmt * stmt, int ndx, double v ){
  BIND_PARAM_CHECK2 {
    int const rc = sqlite3_bind_double( stmt->stmt, ndx, (double)v );
    return rc ? fsl_err_from_db(stmt->db, rc) : 0;
  }
}

int fsl_stmt_bind_blob( fsl_stmt * stmt, int ndx, void const * src,
                        fsl_size_t len, bool makeCopy ){
  BIND_PARAM_CHECK2 {
    int rc;
    rc = sqlite3_bind_blob( stmt->stmt, ndx, src, (int)len,
                            makeCopy ? SQLITE_TRANSIENT : SQLITE_STATIC );
    return rc ? fsl_err_from_db(stmt->db, rc) : 0;
  }
}

int fsl_stmt_bind_text( fsl_stmt * stmt, int ndx, char const * src,
                        fsl_int_t len, bool makeCopy ){
  BIND_PARAM_CHECK {
    int rc;
    if(len<0) len = fsl_strlen((char const *)src);
    rc = sqlite3_bind_text( stmt->stmt, ndx, src, len,
                            makeCopy ? SQLITE_TRANSIENT : SQLITE_STATIC );
    return rc ? fsl_err_from_db(stmt->db, rc) : 0;
  }
}

int fsl_stmt_bind_null_name( fsl_stmt * stmt, char const * param ){
  BIND_PARAM_CHECK{
    return fsl_stmt_bind_null( stmt,
                               sqlite3_bind_parameter_index( stmt->stmt,
                                                             param) );
  }
}

int fsl_stmt_bind_int32_name( fsl_stmt * stmt, char const * param, int32_t v ){
  BIND_PARAM_CHECK {
    return fsl_stmt_bind_int32( stmt,
                                sqlite3_bind_parameter_index( stmt->stmt,
                                                              param),
                                v);
  }
}

int fsl_stmt_bind_int64_name( fsl_stmt * stmt, char const * param, int64_t v ){
  BIND_PARAM_CHECK {
    return fsl_stmt_bind_int64( stmt,
                                sqlite3_bind_parameter_index( stmt->stmt,
                                                              param),
                                v);
  }
}

int fsl_stmt_bind_id_name( fsl_stmt * stmt, char const * param, fsl_id_t v ){
  BIND_PARAM_CHECK {
    return fsl_stmt_bind_id( stmt,
                             sqlite3_bind_parameter_index( stmt->stmt,
                                                           param),
                             v);
  }
}

int fsl_stmt_bind_double_name( fsl_stmt * stmt, char const * param, double v ){
  BIND_PARAM_CHECK {
    return fsl_stmt_bind_double( stmt,
                                 sqlite3_bind_parameter_index( stmt->stmt,
                                                               param),
                                 v);
  }
}

int fsl_stmt_bind_text_name( fsl_stmt * stmt, char const * param,
                             char const * v, fsl_int_t n,
                             bool makeCopy ){
  BIND_PARAM_CHECK {
    return fsl_stmt_bind_text(stmt,
                              sqlite3_bind_parameter_index( stmt->stmt,
                                                            param),
                              v, n, makeCopy);
  }
}

int fsl_stmt_bind_blob_name( fsl_stmt * stmt, char const * param,
                             void const * v, fsl_int_t len,
                             bool makeCopy ){
  BIND_PARAM_CHECK {
    return fsl_stmt_bind_blob(stmt,
                         sqlite3_bind_parameter_index( stmt->stmt,
                                                       param),
                              v, len, makeCopy);
  }
}

int fsl_stmt_param_index( fsl_stmt * stmt, char const * param){
  return (stmt && stmt->stmt)
    ? sqlite3_bind_parameter_index( stmt->stmt, param)
    : -1;
}

#undef BIND_PARAM_CHECK
#undef BIND_PARAM_CHECK2

#define GET_CHECK if(!stmt || !stmt->colCount) return FSL_RC_MISUSE; \
  else if((ndx<0) || (ndx>=stmt->colCount)) return FSL_RC_RANGE; else

int fsl_stmt_get_int32( fsl_stmt * stmt, int ndx, int32_t * v ){
  GET_CHECK {
    if(v) *v = (int32_t)sqlite3_column_int(stmt->stmt, ndx);
    return 0;
  }
}
int fsl_stmt_get_int64( fsl_stmt * stmt, int ndx, int64_t * v ){
  GET_CHECK {
    if(v) *v = (int64_t)sqlite3_column_int64(stmt->stmt, ndx);
    return 0;
  }
}

int fsl_stmt_get_double( fsl_stmt * stmt, int ndx, double * v ){
  GET_CHECK {
    if(v) *v = (double)sqlite3_column_double(stmt->stmt, ndx);
    return 0;
  }
}

int fsl_stmt_get_id( fsl_stmt * stmt, int ndx, fsl_id_t * v ){
  GET_CHECK {
    if(v) *v = (4==sizeof(fsl_id_t))
      ? (fsl_id_t)sqlite3_column_int(stmt->stmt, ndx)
      : (fsl_id_t)sqlite3_column_int64(stmt->stmt, ndx);
    return 0;
  }
}

int fsl_stmt_get_text( fsl_stmt * stmt, int ndx, char const **out,
                       fsl_size_t * outLen ){
  GET_CHECK {
    unsigned char const * t = (out || outLen)
      ? sqlite3_column_text(stmt->stmt, ndx)
      : NULL;
    if(out) *out = (char const *)t;
    if(outLen){
      int const x = sqlite3_column_bytes(stmt->stmt, ndx);
      *outLen = (x>0) ? (fsl_size_t)x : 0;
    }
    return 0;
  }
}

int fsl_stmt_get_blob( fsl_stmt * stmt, int ndx, void const **out,
                       fsl_size_t * outLen ){
  GET_CHECK {
    void const * t = (out || outLen)
      ? sqlite3_column_blob(stmt->stmt, ndx)
      : NULL;
    if(out) *out = t;
    if(outLen){
      if(!t) *outLen = 0;
      else{
        int sz = sqlite3_column_bytes(stmt->stmt, ndx);
        *outLen = (sz>=0) ? (fsl_size_t)sz : 0;
      }
    }
    return 0;
  }
}

#undef GET_CHECK

fsl_id_t fsl_stmt_g_id( fsl_stmt * stmt, int index ){
  fsl_id_t rv = -1;
  fsl_stmt_get_id(stmt, index, &rv);
  return rv;
}
int32_t fsl_stmt_g_int32( fsl_stmt * stmt, int index ){
  int32_t rv = 0;
  fsl_stmt_get_int32(stmt, index, &rv);
  return rv;
}
int64_t fsl_stmt_g_int64( fsl_stmt * stmt, int index ){
  int64_t rv = 0;
  fsl_stmt_get_int64(stmt, index, &rv);
  return rv;
}
double fsl_stmt_g_double( fsl_stmt * stmt, int index ){
  double rv = 0;
  fsl_stmt_get_double(stmt, index, &rv);
  return rv;
}

char const * fsl_stmt_g_text( fsl_stmt * stmt, int index,
                              fsl_size_t * outLen ){
  char const * rv = NULL;
  fsl_stmt_get_text(stmt, index, &rv, outLen);
  return rv;
}


/**
   This function outputs tracing info using fsl_fprintf((FILE*)zFILE,...).
   Defaults to stdout if zFILE is 0.
 */
static void fsl_db_sql_trace(void *zFILE, const char *zSql){
  int const n = fsl_strlen(zSql);
  static int counter = 0;
  /* FIXME: in v1 this uses fossil_trace(), but don't have
     that functionality here yet. */
  fsl_fprintf(zFILE ? (FILE*)zFILE : stdout,
              "SQL TRACE #%d: %s%s\n", ++counter,
              zSql, (n>0 && zSql[n-1]==';') ? "" : ";");
}

/*
   SQL function for debugging.
  
   The print() function writes its arguments to fsl_output()
   if the bound fsl_cx->cxConfig.sqlPrint flag is true.
*/
static void fsl_db_sql_print(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  fsl_cx * f = (fsl_cx*)sqlite3_user_data(context);
  assert(f);
  if( f->cxConfig.sqlPrint ){
    int i;
    for(i=0; i<argc; i++){
      char c = i==argc-1 ? '\n' : ' ';
      fsl_outputf(f, "%s%c", sqlite3_value_text(argv[i]), c);
    }
  }
}

/*
   SQL function to return the number of seconds since 1970.  This is
   the same as strftime('%s','now') but is more compact.
*/
static void fsl_db_now_udf(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  sqlite3_result_int64(context, (sqlite3_int64)time(0));
}

/*
   SQL function to convert a Julian Day to a Unix timestamp.
*/
static void fsl_db_j2u_udf(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  double const jd = (double)sqlite3_value_double(argv[0]);
  sqlite3_result_int64(context, (sqlite3_int64)fsl_julian_to_unix(jd));
}

/*
   SQL function FSL_CKOUT_DIR([bool includeTrailingSlash=1]) returns
   the top-level checkout directory, optionally (by default) with a
   trailing slash. Returns NULL if the fsl_cx instance bound to
   sqlite3_user_data() has no checkout.
*/
static void fsl_db_cx_chkout_dir_udf(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  fsl_cx * f = (fsl_cx*)sqlite3_user_data(context);
  int const includeSlash = argc
    ? sqlite3_value_int(argv[0])
    : 1;
  if(f && f->ckout.dir && f->ckout.dirLen){
    sqlite3_result_text(context, f->ckout.dir,
                        (int)f->ckout.dirLen
                        - (includeSlash ? 0 : 1),
                        SQLITE_TRANSIENT);
  }else{
    sqlite3_result_null(context);
  }
}


/**
    SQL Function to return the check-in time for a file.
    Requires (vid,fid) RID arguments, as described for
    fsl_mtime_of_manifest_file().
 */
static void fsl_db_checkin_mtime_udf(
                                     sqlite3_context *context,
                                     int argc,
                                     sqlite3_value **argv
){
  fsl_cx * f = (fsl_cx*)sqlite3_user_data(context);
  fsl_time_t mtime = 0;
  int rc;
  fsl_id_t vid, fid;
  assert(f);
  vid = (fsl_id_t)sqlite3_value_int(argv[0]);
  fid = (fsl_id_t)sqlite3_value_int(argv[1]);
  rc = fsl_mtime_of_manifest_file(f, vid, fid, &mtime);
  if( rc==0 ){
    sqlite3_result_int64(context, mtime);
  }else{
    sqlite3_result_error(context, "fsl_mtime_of_manifest_file() failed", -1); 
  }
}


static void fsl_db_content_udf(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  fsl_cx * f = (fsl_cx*)sqlite3_user_data(context);
  fsl_id_t rid = 0;
  char const * arg;
  int rc;
  fsl_buffer b = fsl_buffer_empty;
  assert(f);
  if(1 != argc){
    sqlite3_result_error(context, "Expecting one argument", -1);
    return;
  }
  if(SQLITE_INTEGER==sqlite3_value_type(argv[0])){
    rid = (fsl_id_t)sqlite3_value_int64(argv[0]);
    arg = NULL;
  }else{
    arg = (const char*)sqlite3_value_text(argv[0]);
    if(!arg){
      sqlite3_result_error(context, "Invalid argument", -1);
      return;
    }
    rc = fsl_sym_to_rid(f, arg, FSL_SATYPE_CHECKIN, &rid);
    if(rc) goto cx_err;
    else if(!rid){
      sqlite3_result_error(context, "No blob found", -1);
      return;
    }
  }
  rc = fsl_content_get(f, rid, &b);
  if(rc) goto cx_err;
  /* Curiously, i'm seeing no difference in allocation counts here... */
  sqlite3_result_blob(context, b.mem, (int)b.used, fsl_free);
  b = fsl_buffer_empty;
  return;
  cx_err:
  fsl_buffer_clear(&b);
  assert(f->error.msg.used);
  if(FSL_RC_OOM==rc){
    sqlite3_result_error_nomem(context);
  }else{
    assert(f->error.msg.used);
    sqlite3_result_error(context, (char const *)f->error.msg.mem,
                         (int)f->error.msg.used);
  }
}

static void fsl_db_sym2rid_udf(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  fsl_cx * f = (fsl_cx*)sqlite3_user_data(context);
  char const * arg;
  assert(f);
  if(1 != argc){
    sqlite3_result_error(context, "Expecting one argument", -1);
    return;
  }
  arg = (const char*)sqlite3_value_text(argv[0]);
  if(!arg){
    sqlite3_result_error(context, "Expecting a STRING argument", -1);
  }else{
    fsl_id_t rid = 0;
    int const rc = fsl_sym_to_rid(f, arg, FSL_SATYPE_CHECKIN, &rid);
    if(rc){
      if(FSL_RC_OOM==rc){
        sqlite3_result_error_nomem(context);
      }else{
        assert(f->error.msg.used);
        sqlite3_result_error(context, (char const *)f->error.msg.mem,
                             (int)f->error.msg.used);
      }
      fsl_cx_err_reset(f)
        /* This is arguable but keeps this error from poluting
           down-stream code (seen it happen in unit tests).  The irony
           is, it's very possible/likely that the error will propagate
           back up into f->error at some point.
        */;
    }else{
      assert(rid>0);
      sqlite3_result_int64(context, rid);
    }
  }
}

static void fsl_db_dirpart_udf(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  char const * arg;
  int rc;
  fsl_buffer b = fsl_buffer_empty;
  int fSlash = 0;
  if(argc<1 || argc>2){
    sqlite3_result_error(context,
                         "Expecting (string) or (string,bool) arguments",
                         -1);
    return;
  }
  arg = (const char*)sqlite3_value_text(argv[0]);
  if(!arg){
    sqlite3_result_error(context, "Invalid argument", -1);
    return;
  }
  if(argc>1){
    fSlash = sqlite3_value_int(argv[1]);
  }
  rc = fsl_file_dirpart(arg, -1, &b, fSlash ? 1 : 0);
  if(!rc){
    if(b.used && *b.mem){
#if 0
      sqlite3_result_text(context, (char const *)b.mem,
                          (int)b.used, SQLITE_TRANSIENT);
#else
      sqlite3_result_text(context, (char const *)b.mem,
                          (int)b.used, fsl_free);
      b = fsl_buffer_empty /* we passed ^^^^^ on ownership of b.mem */;
#endif
    }else{
      sqlite3_result_null(context);
    }
  }else{
    if(FSL_RC_OOM==rc){
      sqlite3_result_error_nomem(context);
    }else{
      sqlite3_result_error(context, "fsl_dirpart() failed!", -1);
    }
  }
  fsl_buffer_clear(&b);
}


/*
   Implement the user() SQL function.  user() takes no arguments and
   returns the user ID of the current user.
*/
static void fsl_db_user_udf(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  fsl_cx * f = (fsl_cx*)sqlite3_user_data(context);
  assert(f);
  if(f->repo.user){
    sqlite3_result_text(context, f->repo.user, -1, SQLITE_STATIC);
  }else{
    sqlite3_result_null(context);
  }
}

/**
   SQL function:

   fsl_is_enqueued(vfile.id)
   fsl_if_enqueued(vfile.id, X, Y)

   On the commit command, when filenames are specified (in order to do
   a partial commit) the vfile.id values for the named files are
   loaded into the fsl_cx state.  This function looks at that state to
   see if a file is named in that list.

   In the first form (1 argument) return TRUE if either no files are
   named (meaning that all changes are to be committed) or if id is
   found in the list.

   In the second form (3 arguments) return argument X if true and Y if
   false unless Y is NULL, in which case always return X.
*/
static void fsl_db_selected_for_checkin_udf(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  int rc = 0;
  fsl_cx * f = (fsl_cx*)sqlite3_user_data(context);
  fsl_id_bag * bag = &f->ckin.selectedIds;
  assert(argc==1 || argc==3);
  if( bag->entryCount ){
    fsl_id_t const iId = (fsl_id_t)sqlite3_value_int64(argv[0]);
    rc = iId ? (fsl_id_bag_contains(bag, iId) ? 1 : 0) : 0;
  }else{
    rc = 1;
  }
  if(1==argc){
    sqlite3_result_int(context, rc);
  }else{
    assert(3 == argc);
    assert( rc==0 || rc==1 );
    if( sqlite3_value_type(argv[2-rc])==SQLITE_NULL ) rc = 1-rc;
    sqlite3_result_value(context, argv[2-rc]);
  }
}

/**
   fsl_match_vfile_or_dir(p1,p2)

   A helper for resolving expressions like:

   WHERE pathname='X' C OR
      (pathname>'X/' C AND pathname<'X0' C)

   i.e. is 'X' a match for the LHS or is it a directory prefix of
   LHS?

   C = empty or COLLATE NOCASE, depending on the case-sensitivity
   setting of the fsl_cx instance associated with
   sqlite3_user_data(context). p1 is typically vfile.pathname or
   vfile.origname, and p2 is the string being compared against that.

   Resolves to NULL if either argument is NULL, 0 if the comparison
   shown above is false, 1 if the comparison is an exact match, or 2
   if p2 is a directory prefix part of p1.
*/
static void fsl_db_match_vfile_or_dir(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  fsl_cx * f = (fsl_cx*)sqlite3_user_data(context);
  char const * p1;
  char const * p2;
  fsl_buffer * b = 0;
  int rc = 0;
  assert(f);
  if(2 != argc){
    sqlite3_result_error(context, "Expecting two arguments", -1);
    return;
  }
  p1 = (const char*)sqlite3_value_text(argv[0]);
  p2 = (const char*)sqlite3_value_text(argv[1]);
  if(!p1 || !p2){
    sqlite3_result_null(context);
    return;
  }
  int (*cmp)(char const *, char const *) =
    f->cache.caseInsensitive ? fsl_stricmp : fsl_strcmp;
  if(0==cmp(p1, p2)){
    sqlite3_result_int(context, 1);
    return;
  }
  b = fsl_cx_scratchpad(f);
  rc = fsl_buffer_appendf(b, "%s/", p2);
  if(rc) goto oom;
  else if(cmp(p1, fsl_buffer_cstr(b))>0){
    b->mem[b->used-1] = '0';
    if(cmp(p1, fsl_buffer_cstr(b))<0)
    rc = 2;
  }
  assert(0==rc || 2==rc);
  sqlite3_result_int(context, rc);
  end:
  fsl_cx_scratchpad_yield(f, b);
  return;
  oom:
  sqlite3_result_error_nomem(context);
  goto end;
}

fsl_db * fsl_db_malloc(){
  fsl_db * rc = (fsl_db *)fsl_malloc(sizeof(fsl_db));
  if(rc){
    *rc = fsl_db_empty;
    rc->allocStamp = &fsl_db_empty;
  }
  return rc;
}

fsl_stmt * fsl_stmt_malloc(){
  fsl_stmt * rc = (fsl_stmt *)fsl_malloc(sizeof(fsl_stmt));
  if(rc){
    *rc = fsl_stmt_empty;
    rc->allocStamp = &fsl_stmt_empty;
  }
  return rc;
}


/**
    Return true if the schema is out-of-date. db must be an opened
    repo db.
 */
static char fsl_db_repo_schema_is_outofdate(fsl_db *db){
  return fsl_db_exists(db, "SELECT 1 FROM config "
                       "WHERE name='aux-schema' "
                       "AND value<>'%s'",
                       FSL_AUX_SCHEMA);
}

/*
   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.
*/
int fsl_db_repo_verify_schema(fsl_db * db){
  if(fsl_db_repo_schema_is_outofdate(db)) return 1;
  else return fsl_db_exists(db,
                            "SELECT 1 FROM config "
                            "WHERE name='project-code'")
    ? 0 : -1;
}

/**
   Callback for use with sqlite3_commit_hook(). The argument must be a
   (fsl_db*). This function returns 0 only if it surmises that
   fsl_db_transaction_end() triggered the COMMIT. On error it might
   assert() or abort() the application, so this really is just a
   sanity check for something which "must not happen."
*/
static int fsl_db_verify_begin_was_not_called(void * db_fsl){
#if 0
  return 0;
#else
  /* i cannot explain why, but the ptr i'm getting here is most definately
     not a proper fsl_db. */
  fsl_db * db = (fsl_db *)db_fsl;
  assert(db && "What else could it be?");
  assert(db->dbh && "Else we can't have been called by sqlite3, could we have?");
  if(db->beginCount>0){
    fsl_fatal(FSL_RC_MISUSE,"SQL: COMMIT was called from "
              "outside of fsl_db_transaction_end() while a "
              "fsl_db_transaction_begin()-started transaction "
              "is pending.");
    return 2;
  }
  /* we have no context: sqlite3_result_error(context, "fsl_mtime_of_manifest_file() failed", -1);  */
  else return 0;
#endif
}

int fsl_db_open( fsl_db * db, char const * dbFile,
                 int openFlags ){
  int rc;
  fsl_dbh_t * dbh = NULL;
  int isMem = 0;
  if(!db || !dbFile) return FSL_RC_MISUSE;
  else if(db->dbh) return FSL_RC_MISUSE;
  else if(!(isMem = (!*dbFile || 0==fsl_strcmp(":memory:", dbFile)))
          && !(FSL_OPEN_F_CREATE & openFlags)
          && fsl_file_access(dbFile, 0)){
    return fsl_error_set(&db->error, FSL_RC_NOT_FOUND,
                         "DB file not found: %s", dbFile);
  }
  else{
    int sOpenFlags = 0;
    if(isMem){
      sOpenFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
    }else{
      if(FSL_OPEN_F_RO & openFlags){
        sOpenFlags |= SQLITE_OPEN_READONLY;
      }else{
        if(FSL_OPEN_F_RW & openFlags){
          sOpenFlags |= SQLITE_OPEN_READWRITE;
        }
        if(FSL_OPEN_F_CREATE & openFlags){
          sOpenFlags |= SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
        }
        if(!sOpenFlags) sOpenFlags = SQLITE_OPEN_READONLY;
      }
    }
    rc = sqlite3_open_v2( dbFile, &dbh, sOpenFlags, NULL );
    if(rc){
      if(dbh){
        /* By some complete coincidence, FSL_RC_DB==SQLITE_CANTOPEN. */
        rc = fsl_error_set(&db->error, FSL_RC_DB,
                           "Opening db file [%s] failed with "
                           "sqlite code #%d: %s",
                           dbFile, rc, sqlite3_errmsg(dbh));
      }else{
        rc = fsl_error_set(&db->error, FSL_RC_DB,
                           "Opening db file [%s] failed with "
                           "sqlite code #%d",
                           dbFile, rc);
      }
      /* MARKER(("Error msg: %s\n", (char const *)db->error.msg.mem)); */
      goto end;
    }else{
      assert(!db->filename);
      if(!*dbFile || ':'==*dbFile){
        /* assume "" or ":memory:" or some such: don't canonicalize it,
           but copy it nonetheless for consistency. */
        db->filename = fsl_strdup(dbFile);
      }else{
        fsl_buffer tmp = fsl_buffer_empty;
        rc = fsl_file_canonical_name(dbFile, &tmp, 0);
        if(!rc){
          db->filename = (char *)tmp.mem
            /* transfering ownership */;
        }else if(tmp.mem){
          fsl_buffer_clear(&tmp);
        }
      }
      if(rc){
        goto end;
      }else if(!db->filename){
        rc = FSL_RC_OOM;
        goto end;
      }
    }
    db->dbh = dbh;
    if(FSL_OPEN_F_SCHEMA_VALIDATE & openFlags){
      int check;
      check = fsl_db_repo_verify_schema(db);
      if(0 != check){
        rc = (check<0)
          ? fsl_error_set(&db->error, FSL_RC_NOT_A_REPO,
                          "DB file [%s] does not appear to be "
                          "a repository.", dbFile)
          : fsl_error_set(&db->error, FSL_RC_REPO_NEEDS_REBUILD,
                          "DB file [%s] appears to be a fossil "
                          "repsitory, but is out-of-date and needs "
                          "a rebuild.",
                          dbFile)
          ;
        assert(rc == db->error.code);
        goto end;
      }
    }

    if( (openFlags & FSL_OPEN_F_TRACE_SQL)
        || (db->f && db->f->cxConfig.traceSql) ){
      fsl_db_sqltrace_enable(db, stdout);
    }

    if(db->f){
      /*
        Plug in fsl_cx-specific functionality to this one.

        TODO: move this into the fsl_cx code. Its placement here is
        largely historical.
      */
      fsl_cx * f = db->f;
      /* This all comes from db.c:db_open()... */
      /* FIXME: check result codes here. */
      sqlite3_commit_hook(dbh, fsl_db_verify_begin_was_not_called, db);
      sqlite3_busy_timeout(dbh, 5000 /* historical value */);
      sqlite3_wal_autocheckpoint(dbh, 1);  /* Set to checkpoint frequently */
      sqlite3_exec(dbh, "PRAGMA foreign_keys=OFF;", 0, 0, 0);
      sqlite3_create_function(dbh, "now", 0, SQLITE_ANY, 0,
                              fsl_db_now_udf, 0, 0);
      sqlite3_create_function(dbh, "fsl_ci_mtime", 2,
                              SQLITE_ANY | SQLITE_DETERMINISTIC, f,
                              fsl_db_checkin_mtime_udf, 0, 0);
      sqlite3_create_function(dbh, "fsl_user", 0,
                              SQLITE_ANY | SQLITE_DETERMINISTIC, f,
                              fsl_db_user_udf, 0, 0);
      sqlite3_create_function(dbh, "fsl_print", -1,
                              SQLITE_UTF8
                              /* not strictly SQLITE_DETERMINISTIC
                                 because it produces output */,
                              f, fsl_db_sql_print,0,0);
      sqlite3_create_function(dbh, "fsl_content", 1,
                              SQLITE_ANY | SQLITE_DETERMINISTIC, f,
                              fsl_db_content_udf, 0, 0);
      sqlite3_create_function(dbh, "fsl_sym2rid", 1,
                              SQLITE_ANY | SQLITE_DETERMINISTIC, f,
                              fsl_db_sym2rid_udf, 0, 0);
      sqlite3_create_function(dbh, "fsl_dirpart", 1,
                              SQLITE_ANY | SQLITE_DETERMINISTIC, NULL,
                              fsl_db_dirpart_udf, 0, 0);
      sqlite3_create_function(dbh, "fsl_dirpart", 2,
                              SQLITE_ANY | SQLITE_DETERMINISTIC, NULL,
                              fsl_db_dirpart_udf, 0, 0);
      sqlite3_create_function(dbh, "fsl_j2u", 1,
                              SQLITE_ANY | SQLITE_DETERMINISTIC, NULL,
                              fsl_db_j2u_udf, 0, 0);
      /*
        fsl_i[sf]_selected() both require access to the f's list of
        files being considered for commit.
      */
      sqlite3_create_function(dbh, "fsl_is_enqueued", 1, SQLITE_UTF8, f,
                              fsl_db_selected_for_checkin_udf,0,0 );
      sqlite3_create_function(dbh, "fsl_if_enqueued", 3, SQLITE_UTF8, f,
                              fsl_db_selected_for_checkin_udf,0,0 );

      sqlite3_create_function(dbh, "fsl_ckout_dir", -1,
                              SQLITE_ANY /* | SQLITE_DETERMINISTIC ? */,
                              f, fsl_db_cx_chkout_dir_udf,0,0 );
      sqlite3_create_function(dbh, "fsl_match_vfile_or_dir", 2,
                              SQLITE_ANY | SQLITE_DETERMINISTIC,
                              f, fsl_db_match_vfile_or_dir,0,0 );

#if 0
      /* functions registered in v1 by db.c:db_open(). */
      /* porting cgi() requires access to the HTTP/CGI
         layer. i.e. this belongs downstream. */
      sqlite3_create_function(dbh, "cgi", 1, SQLITE_ANY, 0, db_sql_cgi, 0, 0);
      sqlite3_create_function(dbh, "cgi", 2, SQLITE_ANY, 0, db_sql_cgi, 0, 0);
      re_add_sql_func(db) /* Requires the regex bits. */;
#endif
    }/*if(db->f)*/
  }
  end:
  if(rc){
#if 1
    /* This is arguable... */
    if(db->f && db->error.code && !db->f->error.code){
      /* COPY db's error state as f's. */
      fsl_error_copy( &db->error, &db->f->error );
    }
#endif
    if(dbh){
      sqlite3_close(dbh);
      db->dbh = NULL;
    }
  }else{
    assert(db->dbh);
  }
  return rc;
}

int fsl_db_exec_multiv( fsl_db * db, const char * sql, va_list args){
  if(!db || !db->dbh || !sql) return FSL_RC_MISUSE;
  else{
    fsl_buffer buf = fsl_buffer_empty;
    int rc = 0;
    char const * z;
    char const * zEnd = NULL;
    rc = fsl_buffer_appendfv( &buf, sql, args );
    if(rc){
      fsl_buffer_clear(&buf);
      return rc;
    }
    z = fsl_buffer_cstr(&buf);
    while( (SQLITE_OK==rc) && *z ){
      fsl_stmt_t * pStmt = NULL;
      rc = sqlite3_prepare_v2(db->dbh, z, buf.used, &pStmt, &zEnd);
      if( SQLITE_OK != rc ){
        rc = fsl_err_from_db(db, rc);
        break;
      }
      if(pStmt){
        while( SQLITE_ROW == sqlite3_step(pStmt) ){}
        rc = sqlite3_finalize(pStmt);
        if(rc) rc = fsl_err_from_db(db, rc);
      }
      buf.used -= (zEnd-z);
      z = zEnd;
    }
    fsl_buffer_reserve(&buf, 0);
    return rc;
  }
}

int fsl_db_exec_multi( fsl_db * db, const char * sql, ...){
  if(!db || !db->dbh || !sql) return FSL_RC_MISUSE;
  else{
    int rc;
    va_list args;
    va_start(args,sql);
    rc = fsl_db_exec_multiv( db, sql, args );
    va_end(args);
    return rc;
  }
}

int fsl_db_execv( fsl_db * db, const char * sql, va_list args){
  if(!db || !db->dbh || !sql) return FSL_RC_MISUSE;
  else{
    fsl_stmt st = fsl_stmt_empty;
    int rc = 0;
    rc = fsl_db_preparev( db, &st, sql, args );
    if(rc) return rc;
    /* rc = fsl_stmt_step( &st ); */
    while(FSL_RC_STEP_ROW == (rc=fsl_stmt_step(&st))){}
    fsl_stmt_finalize(&st);
    return (FSL_RC_STEP_ERROR==rc)
      ? FSL_RC_DB
      : 0;
  }
}

int fsl_db_exec( fsl_db * db, const char * sql, ...){
  if(!db || !db->dbh || !sql) return FSL_RC_MISUSE;
  else{
    int rc;
    va_list args;
    va_start(args,sql);
    rc = fsl_db_execv( db, sql, args );
    va_end(args);
    return rc;
  }
}

int fsl_db_changes_recent(fsl_db * db){
  return (db && db->dbh)
    ? sqlite3_changes(db->dbh)
    : 0;
}

int fsl_db_changes_total(fsl_db * db){
  return (db && db->dbh)
    ? sqlite3_total_changes(db->dbh)
    : 0;
}

/**
    Sets db->priorChanges to sqlite3_total_changes(db->dbh).
 */
static void fsl_db_reset_change_count(fsl_db * db){
  db->priorChanges = sqlite3_total_changes(db->dbh);
}

int fsl_db_transaction_begin(fsl_db * db){
  if(!db || !db->dbh) return FSL_RC_MISUSE;
  else {
    int rc = (0==db->beginCount)
      ? fsl_db_exec(db,"BEGIN TRANSACTION")
      : 0;
    if(!rc){
      if(1 == ++db->beginCount){
        fsl_db_reset_change_count(db);
      }
    }
    return rc;
  }
}

int fsl_db_transaction_level(fsl_db * db){
  return db->doRollback ? -db->beginCount : db->beginCount;
}

int fsl_db_transaction_commit(fsl_db * db){
  return (db && db->dbh)
    ? fsl_db_transaction_end(db, 0)
    : FSL_RC_MISUSE;
}

int fsl_db_transaction_rollback(fsl_db * db){
  return (db && db->dbh)
    ? fsl_db_transaction_end(db, 1)
    : FSL_RC_MISUSE;
}

int fsl_db_rollback_force( fsl_db * db ){
  if(!db || !db->dbh) return FSL_RC_MISUSE;
  else{
    int rc;
    db->beginCount = 0;
    fsl_db_cleanup_beforeCommit(db);
    rc = fsl_db_exec(db, "ROLLBACK");
    fsl_db_reset_change_count(db);
    return rc;
  }
}

int fsl_db_transaction_end(fsl_db * db, bool doRollback){
  int rc = 0;
  if(!db || !db->dbh) return FSL_RC_MISUSE;
  else if (db->beginCount<=0){
    return fsl_error_set(&db->error, FSL_RC_RANGE,
                         "No transaction is active.");
  }
  if(doRollback) ++db->doRollback
    /* ACHTUNG: note that db->dbRollback is set before
       continuing so that if we return due to a non-0 beginCount
       that the rollback flag propagates through the
       transaction's stack.
    */
    ;
  if(--db->beginCount > 0) return 0;
  assert(0==db->beginCount && "The commit-hook check relies on this.");
  assert(db->doRollback>=0);
  if((0==db->doRollback)
     && (db->priorChanges < sqlite3_total_changes(db->dbh))){
    /* Execute before-commit hooks and leaf checks */
    fsl_size_t x = 0;
    for( ; !rc && (x < db->beforeCommit.used); ++x ){
      char const * sql = (char const *)db->beforeCommit.list[x];
      /* MARKER(("Running before-commit code: [%s]\n", sql)); */
      if(sql) rc = fsl_db_exec_multi( db, "%s", sql );
    }
    if(!rc && db->f && (FSL_DBROLE_REPO & db->role)){
      /*
         i don't like this one bit - this is low-level SCM
         functionality in an otherwise generic routine. Maybe we need
         fsl_cx_transaction_begin/end() instead.

         Much later: we have that routine now but will need to replace
         all relevant calls to fsl_db_transaction_begin()/end() with
         those routines before we can consider moving this there.
      */
      rc = fsl_repo_leaf_do_pending_checks(db->f);
      if(!rc && db->f->cache.toVerify.used){
        rc = fsl_repo_verify_at_commit(db->f);
      }else{
        fsl_repo_verify_cancel(db->f);
      }
    }
    db->doRollback = rc ? 1 : 0;
  }
  fsl_db_cleanup_beforeCommit(db);
  fsl_db_reset_change_count(db);
  rc = fsl_db_exec(db, db->doRollback ? "ROLLBACK" : "COMMIT");
  db->doRollback = 0;
  return rc;
#if 0
  /* original impl, for reference purposes during testing */
  if( g.db==0 ) return;
  if( db.nBegin<=0 ) return;
  if( rollbackFlag ) db.doRollback = 1;
  db.nBegin--;
  if( db.nBegin==0 ){
    int i;
    if( db.doRollback==0 && db.nPriorChanges<sqlite3_total_changes(g.db) ){
      while( db.nBeforeCommit ){
        db.nBeforeCommit--;
        sqlite3_exec(g.db, db.azBeforeCommit[db.nBeforeCommit], 0, 0, 0);
        sqlite3_free(db.azBeforeCommit[db.nBeforeCommit]);
      }
      leaf_do_pending_checks();
    }
    for(i=0; db.doRollback==0 && i<db.nCommitHook; i++){
      db.doRollback |= db.aHook[i].xHook();
    }
    while( db.pAllStmt ){
      db_finalize(db.pAllStmt);
    }
    db_multi_exec(db.doRollback ? "ROLLBACK" : "COMMIT");
    db.doRollback = 0;
  }
#endif  
}

int fsl_db_get_int32v( fsl_db * db, int32_t * rv,
                       char const * sql, va_list args){
  /* Potential fixme: the fsl_db_get_XXX() funcs are 95%
     code duplicates. We "could" replace these with a macro
     or supermacro, though the latter would be problematic
     in the context of an amalgamation build.
  */
  if(!db || !db->dbh || !rv || !sql || !*sql) return FSL_RC_MISUSE;
  else{
    fsl_stmt st = fsl_stmt_empty;
    int rc = 0;
    rc = fsl_db_preparev( db, &st, sql, args );
    if(rc) return rc;
    rc = fsl_stmt_step( &st );
    switch(rc){
      case FSL_RC_STEP_ROW:
        *rv = sqlite3_column_int(st.stmt, 0);
        /* Fall through */
      case FSL_RC_STEP_DONE:
        rc = 0;
        break;
      default:
        assert(FSL_RC_STEP_ERROR==rc);
        break;
    }
    fsl_stmt_finalize(&st);
    return rc;
  }
}

int fsl_db_get_int32( fsl_db * db, int32_t * rv,
                      char const * sql,
                      ... ){
  int rc;
  va_list args;
  va_start(args,sql);
  rc = fsl_db_get_int32v(db, rv, sql, args);
  va_end(args);
  return rc;
}

int fsl_db_get_int64v( fsl_db * db, int64_t * rv,
                       char const * sql, va_list args){
  if(!db || !db->dbh || !rv || !sql || !*sql) return FSL_RC_MISUSE;
  else{
    fsl_stmt st = fsl_stmt_empty;
    int rc = 0;
    rc = fsl_db_preparev( db, &st, sql, args );
    if(rc) return rc;
    rc = fsl_stmt_step( &st );
    switch(rc){
      case FSL_RC_STEP_ROW:
        *rv = sqlite3_column_int64(st.stmt, 0);
        /* Fall through */
      case FSL_RC_STEP_DONE:
        rc = 0;
        break;
      default:
        assert(FSL_RC_STEP_ERROR==rc);
        break;
    }
    fsl_stmt_finalize(&st);
    return rc;
  }
}

int fsl_db_get_int64( fsl_db * db, int64_t * rv,
                      char const * sql, ... ){
  int rc;
  va_list args;
  va_start(args,sql);
  rc = fsl_db_get_int64v(db, rv, sql, args);
  va_end(args);
  return rc;
}


int fsl_db_get_idv( fsl_db * db, fsl_id_t * rv,
                       char const * sql, va_list args){
  if(!db || !db->dbh || !rv || !sql || !*sql) return FSL_RC_MISUSE;
  else{
    fsl_stmt st = fsl_stmt_empty;
    int rc = 0;
    rc = fsl_db_preparev( db, &st, sql, args );
    if(rc) return rc;
    rc = fsl_stmt_step( &st );
    switch(rc){
      case FSL_RC_STEP_ROW:
        *rv = (fsl_id_t)sqlite3_column_int64(st.stmt, 0);
        /* Fall through */
      case FSL_RC_STEP_DONE:
        rc = 0;
        break;
      default:
        assert(FSL_RC_STEP_ERROR==rc);
        break;
    }
    fsl_stmt_finalize(&st);
    return rc;
  }
}

int fsl_db_get_id( fsl_db * db, fsl_id_t * rv,
                      char const * sql, ... ){
  int rc;
  va_list args;
  va_start(args,sql);
  rc = fsl_db_get_idv(db, rv, sql, args);
  va_end(args);
  return rc;
}


int fsl_db_get_sizev( fsl_db * db, fsl_size_t * rv,
                      char const * sql, va_list args){
  if(!db || !db->dbh || !rv || !sql || !*sql) return FSL_RC_MISUSE;
  else{
    fsl_stmt st = fsl_stmt_empty;
    int rc = 0;
    rc = fsl_db_preparev( db, &st, sql, args );
    if(rc) return rc;
    rc = fsl_stmt_step( &st );
    switch(rc){
      case FSL_RC_STEP_ROW:{
        sqlite3_int64 const i = sqlite3_column_int64(st.stmt, 0);
        if(i<0){
          rc = FSL_RC_RANGE;
          break;
        }
        *rv = (fsl_size_t)i;
        rc = 0;
        break;
      }
      case FSL_RC_STEP_DONE:
        rc = 0;
        break;
      default:
        assert(FSL_RC_STEP_ERROR==rc);
        break;
    }
    fsl_stmt_finalize(&st);
    return rc;
  }
}

int fsl_db_get_size( fsl_db * db, fsl_size_t * rv,
                      char const * sql, ... ){
  int rc;
  va_list args;
  va_start(args,sql);
  rc = fsl_db_get_sizev(db, rv, sql, args);
  va_end(args);
  return rc;
}


int fsl_db_get_doublev( fsl_db * db, double * rv,
                       char const * sql, va_list args){
  if(!db || !db->dbh || !rv || !sql || !*sql) return FSL_RC_MISUSE;
  else{
    fsl_stmt st = fsl_stmt_empty;
    int rc = 0;
    rc = fsl_db_preparev( db, &st, sql, args );
    if(rc) return rc;
    rc = fsl_stmt_step( &st );
    switch(rc){
      case FSL_RC_STEP_ROW:
        *rv = sqlite3_column_double(st.stmt, 0);
        /* Fall through */
      case FSL_RC_STEP_DONE:
        rc = 0;
        break;
      default:
        assert(FSL_RC_STEP_ERROR==rc);
        break;
    }
    fsl_stmt_finalize(&st);
    return rc;
  }
}

int fsl_db_get_double( fsl_db * db, double * rv,
                      char const * sql,
                      ... ){
  int rc;
  va_list args;
  va_start(args,sql);
  rc = fsl_db_get_doublev(db, rv, sql, args);
  va_end(args);
  return rc;
}


int fsl_db_get_textv( fsl_db * db, char ** rv,
                      fsl_size_t *rvLen,
                      char const * sql, va_list args){
  if(!db || !db->dbh || !rv || !sql || !*sql) return FSL_RC_MISUSE;
  else{
    fsl_stmt st = fsl_stmt_empty;
    int rc = 0;
    rc = fsl_db_preparev( db, &st, sql, args );
    if(rc) return rc;
    rc = fsl_stmt_step( &st );
    switch(rc){
      case FSL_RC_STEP_ROW:{
        char const * str = (char const *)sqlite3_column_text(st.stmt, 0);
        int const len = sqlite3_column_bytes(st.stmt,0);
        if(!str){
          *rv = NULL;
          if(rvLen) *rvLen = 0;
        }else{
          char * x = fsl_strndup(str, len);
          if(!x){
            rc = FSL_RC_OOM;
          }else{
            *rv = x;
            if(rvLen) *rvLen = (fsl_size_t)len;
            rc = 0;
          }
        }
        break;
      }
      case FSL_RC_STEP_DONE:
        *rv = NULL;
        if(rvLen) *rvLen = 0;
        rc = 0;
        break;
      default:
        assert(FSL_RC_STEP_ERROR==rc);
        break;
    }
    fsl_stmt_finalize(&st);
    return rc;
  }
}

int fsl_db_get_text( fsl_db * db, char ** rv,
                     fsl_size_t * rvLen,
                     char const * sql, ... ){
  int rc;
  va_list args;
  va_start(args,sql);
  rc = fsl_db_get_textv(db, rv, rvLen, sql, args);
  va_end(args);
  return rc;
}

int fsl_db_get_blobv( fsl_db * db, void ** rv,
                      fsl_size_t *rvLen,
                      char const * sql, va_list args){
  if(!db || !db->dbh || !rv || !sql || !*sql) return FSL_RC_MISUSE;
  else{
    fsl_stmt st = fsl_stmt_empty;
    int rc = 0;
    rc = fsl_db_preparev( db, &st, sql, args );
    if(rc) return rc;
    rc = fsl_stmt_step( &st );
    switch(rc){
      case FSL_RC_STEP_ROW:{
        fsl_buffer buf = fsl_buffer_empty;
        void const * str = sqlite3_column_blob(st.stmt, 0);
        int const len = sqlite3_column_bytes(st.stmt,0);
        if(!str){
          *rv = NULL;
          if(rvLen) *rvLen = 0;
        }else{
          rc = fsl_buffer_append(&buf, str, len);
          if(!rc){
            *rv = buf.mem;
            if(rvLen) *rvLen = buf.used;
          }
        }
        break;
      }
      case FSL_RC_STEP_DONE:
        *rv = NULL;
        if(rvLen) *rvLen = 0;
        rc = 0;
        break;
      default:
        assert(FSL_RC_STEP_ERROR==rc);
        break;
    }
    fsl_stmt_finalize(&st);
    return rc;
  }
}

int fsl_db_get_blob( fsl_db * db, void ** rv,
                     fsl_size_t * rvLen,
                     char const * sql, ... ){
  int rc;
  va_list args;
  va_start(args,sql);
  rc = fsl_db_get_blobv(db, rv, rvLen, sql, args);
  va_end(args);
  return rc;
}

int fsl_db_get_bufferv( fsl_db * db, fsl_buffer * b,
                        char asBlob, char const * sql,
                        va_list args){
  if(!db || !db->dbh || !b || !sql || !*sql) return FSL_RC_MISUSE;
  else{
    fsl_stmt st = fsl_stmt_empty;
    int rc = 0;
    rc = fsl_db_preparev( db, &st, sql, args );
    if(rc) return rc;
    rc = fsl_stmt_step( &st );
    switch(rc){
      case FSL_RC_STEP_ROW:{
        void const * str = asBlob
          ? sqlite3_column_blob(st.stmt, 0)
          : (void const *)sqlite3_column_text(st.stmt, 0);
        int const len = sqlite3_column_bytes(st.stmt,0);
        rc = 0;
        b->used = 0;
        rc = fsl_buffer_append( b, str, len );
        break;
      }
      case FSL_RC_STEP_DONE:
        rc = 0;
        break;
      default:
        assert(FSL_RC_STEP_ERROR==rc);
        break;
    }
    fsl_stmt_finalize(&st);
    return rc;
  }
}

int fsl_db_get_buffer( fsl_db * db, fsl_buffer * b,
                       char asBlob,
                       char const * sql, ... ){
  int rc;
  va_list args;
  va_start(args,sql);
  rc = fsl_db_get_bufferv(db, b, asBlob, sql, args);
  va_end(args);
  return rc;
}

int32_t fsl_db_g_int32( fsl_db * db, int32_t dflt,
                            char const * sql,
                            ... ){
  int32_t rv = dflt;
  va_list args;
  va_start(args,sql);
  fsl_db_get_int32v(db, &rv, sql, args);
  va_end(args);
  return rv;
}

int64_t fsl_db_g_int64( fsl_db * db, int64_t dflt,
                            char const * sql,
                            ... ){
  int64_t rv = dflt;
  va_list args;
  va_start(args,sql);
  fsl_db_get_int64v(db, &rv, sql, args);
  va_end(args);
  return rv;
}

fsl_id_t fsl_db_g_id( fsl_db * db, fsl_id_t dflt,
                            char const * sql,
                            ... ){
  fsl_id_t rv = dflt;
  va_list args;
  va_start(args,sql);
  fsl_db_get_idv(db, &rv, sql, args);
  va_end(args);
  return rv;
}

fsl_size_t fsl_db_g_size( fsl_db * db, fsl_size_t dflt,
                        char const * sql,
                        ... ){
  fsl_size_t rv = dflt;
  va_list args;
  va_start(args,sql);
  fsl_db_get_sizev(db, &rv, sql, args);
  va_end(args);
  return rv;
}

double fsl_db_g_double( fsl_db * db, double dflt,
                              char const * sql,
                              ... ){
  double rv = dflt;
  va_list args;
  va_start(args,sql);
  fsl_db_get_doublev(db, &rv, sql, args);
  va_end(args);
  return rv;
}

char * fsl_db_g_text( fsl_db * db, fsl_size_t * len,
                      char const * sql,
                      ... ){
  char * rv = NULL;
  va_list args;
  va_start(args,sql);
  fsl_db_get_textv(db, &rv, len, sql, args);
  va_end(args);
  return rv;
}

void * fsl_db_g_blob( fsl_db * db, fsl_size_t * len,
                      char const * sql,
                      ... ){
  void * rv = NULL;
  va_list args;
  va_start(args,sql);
  fsl_db_get_blob(db, &rv, len, sql, args);
  va_end(args);
  return rv;
}

double fsl_db_julian_now(fsl_db * db){
  double rc = -1.0;
  if(db && db->dbh){
    /* TODO? use cached statement? So far not used often enough to
       justify it. */
    fsl_db_get_double( db, &rc, "SELECT julianday('now')");
  }
  return rc;
}

double fsl_db_string_to_julian(fsl_db * db, char const * str){
  double rc = -1.0;
  if(db && db->dbh){
    /* TODO? use cached statement? So far not used often enough to
       justify it. */
    fsl_db_get_double( db, &rc, "SELECT julianday(%Q)",str);
  }
  return rc;
}

bool fsl_db_existsv(fsl_db * db, char const * sql, va_list args ){
  if(!db || !db->dbh || !sql) return 0;
  else if(!*sql) return 0;
  else{
    fsl_stmt st = fsl_stmt_empty;
    bool rv = false;
    if(!fsl_db_preparev(db, &st, sql, args)){
      rv = FSL_RC_STEP_ROW==fsl_stmt_step(&st) ? true : false;
    }
    fsl_stmt_finalize(&st);
    return rv;
  }

}

bool fsl_db_exists(fsl_db * db, char const * sql, ... ){
  bool rc;
  va_list args;
  va_start(args,sql);
  rc = fsl_db_existsv(db, sql, args);
  va_end(args);
  return rc;
}

/*
** Return TRUE if zTable exists.
*/
bool fsl_db_table_exists(fsl_db * db,
                        fsl_dbrole_e whichDb,
                        const char *zTable
){
  const char *zDb = fsl_db_role_label( whichDb );
  int rc = db->dbh
    ? sqlite3_table_column_metadata(db->dbh, zDb, zTable, 0,
                                    0, 0, 0, 0, 0)
    : !SQLITE_OK;
  return rc==SQLITE_OK ? true : false;
}


/*
   Returns non-0 if the database (which must be open) table identified
   by zTableName has a column named zColName (case-sensitive), else
   returns 0.
*/
char fsl_db_table_has_column( fsl_db * db, char const *zTableName, char const *zColName ){
  fsl_stmt q = fsl_stmt_empty;
  int rc = 0;
  char rv = 0;
  if(!db || !zTableName || !*zTableName || !zColName || !*zColName) return 0;
  rc = fsl_db_prepare(db, &q, "PRAGMA table_info(%Q)", zTableName );
  if(!rc) while(FSL_RC_STEP_ROW==fsl_stmt_step(&q)){
    /* Columns: (cid, name, type, notnull, dflt_value, pk) */
    fsl_size_t colLen = 0;
    char const * zCol = fsl_stmt_g_text(&q, 1, &colLen);
    if(0==fsl_strncmp(zColName, zCol, colLen)){
      rv = 1;
      break;
    }
  }
  fsl_stmt_finalize(&q);
  return rv;
}

char * fsl_db_random_hex(fsl_db * db, fsl_size_t n){
  if(!db || !n) return NULL;
  else{
    fsl_size_t rvLen = 0;
    char * rv = fsl_db_g_text(db, &rvLen,
                              "SELECT lower(hex("
                              "randomblob(%"FSL_SIZE_T_PFMT")))",
                              (fsl_size_t)(n/2+1));
    if(rv){
      assert(rvLen>=n);
      rv[n]=0;
    }
    return rv;
  }
}


int fsl_db_select_slistv( fsl_db * db, fsl_list * tgt,
                          char const * fmt, va_list args ){
  if(!db || !tgt || !fmt) return FSL_RC_MISUSE;
  else if(!*fmt) return FSL_RC_RANGE;
  else{
    int rc;
    fsl_stmt st = fsl_stmt_empty;
    fsl_size_t nlen;
    char const * n;
    char * cp;
    rc = fsl_db_preparev(db, &st, fmt, args);
    while( !rc && (FSL_RC_STEP_ROW==fsl_stmt_step(&st)) ){
      nlen = 0;
      n = fsl_stmt_g_text(&st, 0, &nlen);
      cp = n ? fsl_strndup(n, (fsl_int_t)nlen) : NULL;
      if(n && !cp) rc = FSL_RC_OOM;
      else{
        rc = fsl_list_append(tgt, cp);
        if(rc && cp) fsl_free(cp);
      }
    }
    fsl_stmt_finalize(&st);
    return rc;
  }
}

int fsl_db_select_slist( fsl_db * db, fsl_list * tgt,
                         char const * fmt, ... ){
  int rc;
  va_list va;
  va_start (va,fmt);
  rc = fsl_db_select_slistv(db, tgt, fmt, va);
  va_end(va);
  return rc;
}

void fsl_db_sqltrace_enable( fsl_db * db, FILE * outStream ){
  if(db && db->dbh){
    sqlite3_trace(db->dbh, fsl_db_sql_trace, outStream);
  }
}

int fsl_db_init( fsl_error * err,
                 char const * zFilename,
                 char const * zSchema,
                 ... ){
  fsl_db DB = fsl_db_empty;
  fsl_db * db = &DB;
  char const * zSql;
  int rc;
  char inTrans = 0;
  va_list ap;
  rc = fsl_db_open(db, zFilename, 0);
  if(rc) goto end;
  rc = fsl_db_exec(db, "BEGIN EXCLUSIVE");
  if(rc) goto end;
  inTrans = 1;
  rc = fsl_db_exec_multi(db, "%s", zSchema);
  if(rc) goto end;
  va_start(ap, zSchema);
  while( !rc && (zSql = va_arg(ap, const char*))!=NULL ){
    rc = fsl_db_exec_multi(db, "%s", zSql);
  }
  va_end(ap);
  end:
  if(rc){
    if(inTrans) fsl_db_exec(db, "ROLLBACK");
  }else{
    rc = fsl_db_exec(db, "COMMIT");
  }
  if(err){
    if(db->error.code){
      fsl_error_move(&db->error, err);
    }else if(rc){
      err->code = rc;
      err->msg.used = 0;
    }
  }
  fsl_db_close(db);
  return rc;
}

int fsl_stmt_each_f_dump( fsl_stmt * stmt, void * state ){
  int i;
  fsl_cx * f = (stmt && stmt->db) ? stmt->db->f : NULL;
  char const * sep = "\t";
  if(!f) return FSL_RC_MISUSE;
  if(1==stmt->rowCount){
    for( i = 0; i < stmt->colCount; ++i ){
      fsl_outputf(f, "%s%s", fsl_stmt_col_name(stmt, i),
            (i==stmt->colCount-1) ? "" : sep);
    }
    fsl_output(f, "\n", 1);
  }
  for( i = 0; i < stmt->colCount; ++i ){
    char const * val = fsl_stmt_g_text(stmt, i, NULL);
    fsl_outputf(f, "%s%s", val ? val : "NULL",
          (i==stmt->colCount-1) ? "" : sep);
  }
  fsl_output(f, "\n", 1);
  return 0;
}


#undef MARKER
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/deck.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
4004
4005
4006
4007
4008
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
4021
4022
4023
4024
4025
4026
4027
4028
4029
4030
4031
4032
4033
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151
4152
4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235
4236
4237
4238
4239
4240
4241
4242
4243
4244
4245
4246
4247
4248
4249
4250
4251
4252
4253
4254
4255
4256
4257
4258
4259
4260
4261
4262
4263
4264
4265
4266
4267
4268
4269
4270
4271
4272
4273
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
4288
4289
4290
4291
4292
4293
4294
4295
4296
4297
4298
4299
4300
4301
4302
4303
4304
4305
4306
4307
4308
4309
4310
4311
4312
4313
4314
4315
4316
4317
4318
4319
4320
4321
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331
4332
4333
4334
4335
4336
4337
4338
4339
4340
4341
4342
4343
4344
4345
4346
4347
4348
4349
4350
4351
4352
4353
4354
4355
4356
4357
4358
4359
4360
4361
4362
4363
4364
4365
4366
4367
4368
4369
4370
4371
4372
4373
4374
4375
4376
4377
4378
4379
4380
4381
4382
4383
4384
4385
4386
4387
4388
4389
4390
4391
4392
4393
4394
4395
4396
4397
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
4417
4418
4419
4420
4421
4422
4423
4424
4425
4426
4427
4428
4429
4430
4431
4432
4433
4434
4435
4436
4437
4438
4439
4440
4441
4442
4443
4444
4445
4446
4447
4448
4449
4450
4451
4452
4453
4454
4455
4456
4457
4458
4459
4460
4461
4462
4463
4464
4465
4466
4467
4468
4469
4470
4471
4472
4473
4474
4475
4476
4477
4478
4479
4480
4481
4482
4483
4484
4485
4486
4487
4488
4489
4490
4491
4492
4493
4494
4495
4496
4497
4498
4499
4500
4501
4502
4503
4504
4505
4506
4507
4508
4509
4510
4511
4512
4513
4514
4515
4516
4517
4518
4519
4520
4521
4522
4523
4524
4525
4526
4527
4528
4529
4530
4531
4532
4533
4534
4535
4536
4537
4538
4539
4540
4541
4542
4543
4544
4545
4546
4547
4548
4549
4550
4551
4552
4553
4554
4555
4556
4557
4558
4559
4560
4561
4562
4563
4564
4565
4566
4567
4568
4569
4570
4571
4572
4573
4574
4575
4576
4577
4578
4579
4580
4581
4582
4583
4584
4585
4586
4587
4588
4589
4590
4591
4592
4593
4594
4595
4596
4597
4598
4599
4600
4601
4602
4603
4604
4605
4606
4607
4608
4609
4610
4611
4612
4613
4614
4615
4616
4617
4618
4619
4620
4621
4622
4623
4624
4625
4626
4627
4628
4629
4630
4631
4632
4633
4634
4635
4636
4637
4638
4639
4640
4641
4642
4643
4644
4645
4646
4647
4648
4649
4650
4651
4652
4653
4654
4655
4656
4657
4658
4659
4660
4661
4662
4663
4664
4665
4666
4667
4668
4669
4670
4671
4672
4673
4674
4675
4676
4677
4678
4679
4680
4681
4682
4683
4684
4685
4686
4687
4688
4689
4690
4691
4692
4693
4694
4695
4696
4697
4698
4699
4700
4701
4702
4703
4704
4705
4706
4707
4708
4709
4710
4711
4712
4713
4714
4715
4716
4717
4718
4719
4720
4721
4722
4723
4724
4725
4726
4727
4728
4729
4730
4731
4732
4733
4734
4735
4736
4737
4738
4739
4740
4741
4742
4743
4744
4745
4746
4747
4748
4749
4750
4751
4752
4753
4754
4755
4756
4757
4758
4759
4760
4761
4762
4763
4764
4765
4766
4767
4768
4769
4770
4771
4772
4773
4774
4775
4776
4777
4778
4779
4780
4781
4782
4783
4784
4785
4786
4787
4788
4789
4790
4791
4792
4793
4794
4795
4796
4797
4798
4799
4800
4801
4802
4803
4804
4805
4806
4807
4808
4809
4810
4811
4812
4813
4814
4815
4816
4817
4818
4819
4820
4821
4822
4823
4824
4825
4826
4827
4828
4829
4830
4831
4832
4833
4834
4835
4836
4837
4838
4839
4840
4841
4842
4843
4844
4845
4846
4847
4848
4849
4850
4851
4852
4853
4854
4855
4856
4857
4858
4859
4860
4861
4862
4863
4864
4865
4866
4867
4868
4869
4870
4871
4872
4873
4874
4875
4876
4877
4878
4879
4880
4881
4882
4883
4884
4885
4886
4887
4888
4889
4890
4891
4892
4893
4894
4895
4896
4897
4898
4899
4900
4901
4902
4903
4904
4905
4906
4907
4908
4909
4910
4911
4912
4913
4914
4915
4916
4917
4918
4919
4920
4921
4922
4923
4924
4925
4926
4927
4928
4929
4930
4931
4932
4933
4934
4935
4936
4937
4938
4939
4940
4941
4942
4943
4944
4945
4946
4947
4948
4949
4950
4951
4952
4953
4954
4955
4956
4957
4958
4959
4960
4961
4962
4963
4964
4965
4966
4967
4968
4969
4970
4971
4972
4973
4974
4975
4976
4977
4978
4979
4980
4981
4982
4983
4984
4985
4986
4987
4988
4989
4990
4991
4992
4993
4994
4995
4996
4997
4998
4999
5000
5001
5002
5003
5004
5005
5006
5007
5008
5009
5010
5011
5012
5013
5014
5015
5016
5017
5018
5019
5020
5021
5022
5023
5024
5025
5026
5027
5028
5029
5030
5031
5032
5033
5034
5035
5036
5037
5038
5039
5040
5041
5042
5043
5044
5045
5046
5047
5048
5049
5050
5051
5052
5053
5054
5055
5056
5057
5058
5059
5060
5061
5062
5063
5064
5065
5066
5067
5068
5069
5070
5071
5072
5073
5074
5075
5076
5077
5078
5079
5080
5081
5082
5083
5084
5085
5086
5087
5088
5089
5090
5091
5092
5093
5094
5095
5096
5097
5098
5099
5100
5101
5102
5103
5104
5105
5106
5107
5108
5109
5110
5111
5112
5113
5114
5115
5116
5117
5118
5119
5120
5121
5122
5123
5124
5125
5126
5127
5128
5129
5130
5131
5132
5133
5134
5135
5136
5137
5138
5139
5140
5141
5142
5143
5144
5145
5146
5147
5148
5149
5150
5151
5152
5153
5154
5155
5156
5157
5158
5159
5160
5161
5162
5163
5164
5165
5166
5167
5168
5169
5170
5171
5172
5173
5174
5175
5176
5177
5178
5179
5180
5181
5182
5183
5184
5185
5186
5187
5188
5189
5190
5191
5192
5193
5194
5195
5196
5197
5198
5199
5200
5201
5202
5203
5204
5205
5206
5207
5208
5209
5210
5211
5212
5213
5214
5215
5216
5217
5218
5219
5220
5221
5222
5223
5224
5225
5226
5227
5228
5229
5230
5231
5232
5233
5234
5235
5236
5237
5238
5239
5240
5241
5242
5243
5244
5245
5246
5247
5248
5249
5250
5251
5252
5253
5254
5255
5256
5257
5258
5259
5260
5261
5262
5263
5264
5265
5266
5267
5268
5269
5270
5271
5272
5273
5274
5275
5276
5277
5278
5279
5280
5281
5282
5283
5284
5285
5286
5287
5288
5289
5290
5291
5292
5293
5294
5295
5296
5297
5298
5299
5300
5301
5302
5303
5304
5305
5306
5307
5308
5309
5310
5311
5312
5313
5314
5315
5316
5317
5318
5319
5320
5321
5322
5323
5324
5325
5326
5327
5328
5329
5330
5331
5332
5333
5334
5335
5336
5337
5338
5339
5340
5341
5342
5343
5344
5345
5346
5347
5348
5349
5350
5351
5352
5353
5354
5355
5356
5357
5358
5359
5360
5361
5362
5363
5364
5365
5366
5367
5368
5369
5370
5371
5372
5373
5374
5375
5376
5377
5378
5379
5380
5381
5382
5383
5384
5385
5386
5387
5388
5389
5390
5391
5392
5393
5394
5395
5396
5397
5398
5399
5400
5401
5402
5403
5404
5405
5406
5407
5408
5409
5410
5411
5412
5413
5414
5415
5416
5417
5418
5419
5420
5421
5422
5423
5424
5425
5426
5427
5428
5429
5430
5431
5432
5433
5434
5435
5436
5437
5438
5439
5440
5441
5442
5443
5444
5445
5446
5447
5448
5449
5450
5451
5452
5453
5454
5455
5456
5457
5458
5459
5460
5461
5462
5463
5464
5465
5466
5467
5468
5469
5470
5471
5472
5473
5474
5475
5476
5477
5478
5479
5480
5481
5482
5483
5484
5485
5486
5487
5488
5489
5490
5491
5492
5493
5494
5495
5496
5497
5498
5499
5500
5501
5502
5503
5504
5505
5506
5507
5508
5509
5510
5511
5512
5513
5514
5515
5516
5517
5518
5519
5520
5521
5522
5523
5524
5525
5526
5527
5528
5529
5530
5531
5532
5533
5534
5535
5536
5537
5538
5539
5540
5541
5542
5543
5544
5545
5546
5547
5548
5549
5550
5551
5552
5553
5554
5555
5556
5557
5558
5559
5560
5561
5562
5563
5564
5565
5566
5567
5568
5569
5570
5571
5572
5573
5574
5575
5576
5577
5578
5579
5580
5581
5582
5583
5584
5585
5586
5587
5588
5589
5590
5591
5592
5593
5594
5595
5596
5597
5598
5599
5600
5601
5602
5603
5604
5605
5606
5607
5608
5609
5610
5611
5612
5613
5614
5615
5616
5617
5618
5619
5620
5621
5622
5623
5624
5625
5626
5627
5628
5629
5630
5631
5632
5633
5634
5635
5636
5637
5638
5639
5640
5641
5642
5643
5644
5645
5646
5647
5648
5649
5650
5651
5652
5653
5654
5655
5656
5657
5658
5659
5660
5661
5662
5663
5664
5665
5666
5667
5668
5669
5670
5671
5672
5673
5674
5675
5676
5677
5678
5679
5680
5681
5682
5683
5684
5685
5686
5687
5688
5689
5690
5691
5692
5693
5694
5695
5696
5697
5698
5699
5700
5701
5702
5703
5704
5705
5706
5707
5708
5709
5710
5711
5712
5713
5714
5715
5716
5717
5718
5719
5720
5721
5722
5723
5724
5725
5726
5727
5728
5729
5730
5731
5732
5733
5734
5735
5736
5737
5738
5739
5740
5741
5742
5743
5744
5745
5746
5747
5748
5749
5750
5751
5752
5753
5754
5755
5756
5757
5758
5759
5760
5761
5762
5763
5764
5765
5766
5767
5768
5769
5770
5771
5772
5773
5774
5775
5776
5777
5778
5779
5780
5781
5782
5783
5784
5785
5786
5787
5788
5789
5790
5791
5792
5793
5794
5795
5796
5797
5798
5799
5800
5801
5802
5803
5804
5805
5806
5807
5808
5809
5810
5811
5812
5813
5814
5815
5816
5817
5818
5819
5820
5821
5822
5823
5824
5825
5826
5827
5828
5829
5830
5831
5832
5833
5834
5835
5836
5837
5838
5839
5840
5841
5842
5843
5844
5845
5846
5847
5848
5849
5850
5851
5852
5853
5854
5855
5856
5857
5858
5859
5860
5861
5862
5863
5864
5865
5866
5867
5868
5869
5870
5871
5872
5873
5874
5875
5876
5877
5878
5879
5880
5881
5882
5883
5884
5885
5886
5887
5888
5889
5890
5891
5892
5893
5894
5895
5896
5897
5898
5899
5900
5901
5902
5903
5904
5905
5906
5907
5908
5909
5910
5911
5912
5913
5914
5915
5916
5917
5918
5919
5920
5921
5922
5923
5924
5925
5926
5927
5928
5929
5930
5931
5932
5933
5934
5935
5936
5937
5938
5939
5940
5941
5942
5943
5944
5945
5946
5947
5948
5949
5950
5951
5952
5953
5954
5955
5956
5957
5958
5959
5960
5961
5962
5963
5964
5965
5966
5967
5968
5969
5970
5971
5972
5973
5974
5975
5976
5977
5978
5979
5980
5981
5982
5983
5984
5985
5986
5987
5988
5989
5990
5991
5992
5993
5994
5995
5996
5997
5998
5999
6000
6001
6002
6003
6004
6005
6006
6007
6008
6009
6010
6011
6012
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  Copyright 2013-2021 Stephan Beal (https://wanderinghorse.net).


  
  This program is free software; you can redistribute it and/or
  modify it under the terms of the Simplified BSD License (also
  known as the "2-Clause License" or "FreeBSD License".)
  
  This program is distributed in the hope that it will be useful,
  but without any warranty; without even the implied warranty of
  merchantability or fitness for a particular purpose.
  
  *****************************************************************************
  This file houses the manifest/control-artifact-related APIs.
*/
#include "fossil-scm/fossil-internal.h"
#include "fossil-scm/fossil-hash.h"
#include "fossil-scm/fossil-forum.h"
#include "fossil-scm/fossil-confdb.h"
#include <assert.h>
#include <stdlib.h> /* qsort() */
#include <memory.h> /* memcmp() */

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

typedef int StaticAssertMCacheArraySizes[
 ((sizeof(fsl_mcache_empty.aAge)
  /sizeof(fsl_mcache_empty.aAge[0]))
 == (sizeof(fsl_mcache_empty.decks)
     /sizeof(fsl_mcache_empty.decks[0])))
 ? 1 : -1
];

enum fsl_card_F_list_flags_e {
FSL_CARD_F_LIST_NEEDS_SORT = 0x01
};

/**
   Transfers the contents of d into f->cache.mcache. If d is
   dynamically allocated then it is also freed. In any case, after
   calling this the caller must behave as if the deck had been passed
   to fsl_deck_finalize().

   If manifest caching is disabled for f, d is immediately finalized.
*/
static void fsl_cx_mcache_insert(fsl_cx *f, fsl_deck * d){
  if(!(f->flags & FSL_CX_F_MANIFEST_CACHE)){
    fsl_deck_finalize(d);
    return;
  }
  const unsigned cacheLen =
    (unsigned)(sizeof(fsl_mcache_empty.aAge)
               /sizeof(fsl_mcache_empty.aAge[0]));
  fsl_mcache * const mc = &f->cache.mcache;
  while( d ){
    unsigned i;
    fsl_deck *pBaseline = d->B.baseline;
    d->B.baseline = 0;
    for(i=0; i<cacheLen; ++i){
      if( !mc->decks[i].rid ) break;
    }
    if( i>=cacheLen ){
      unsigned oldest = 0;
      unsigned oldestAge = mc->aAge[0];
      for(i=1; i<cacheLen; ++i){
        if( mc->aAge[i]<oldestAge ){
          oldest = i;
          oldestAge = mc->aAge[i];
        }
      }
      fsl_deck_finalize(&mc->decks[oldest]);
      i = oldest;
    }
    mc->aAge[i] = ++mc->nextAge;
    mc->decks[i] = *d;
    *d = fsl_deck_empty;
    if(&fsl_deck_empty == mc->decks[i].allocStamp){
      /* d was fsl_deck_malloc()'d so we need to free it, but cannot
         send it through fsl_deck_finalize() because that would try to
         clean up the memory we just transferred ownership of to
         mc->decks[i]. So... */
      mc->decks[i].allocStamp = 0;
      fsl_free(d);
    }
    d = pBaseline;
  }
}

/**
   Searches f->cache.mcache for a deck with the given RID. If found,
   it is bitwise copied over tgt, that entry is removed from the
   cache, and true is returned. If no match is found, tgt is not
   modified and false is returned.

   If manifest caching is disabled for f, false is immediately
   returned without causing side effects.
*/
static bool fsl_cx_mcache_search(fsl_cx * f, fsl_id_t rid, fsl_deck * tgt){
  if(!(f->flags & FSL_CX_F_MANIFEST_CACHE)) return false;
  const unsigned cacheLen =
    (int)(sizeof(fsl_mcache_empty.aAge)
          /sizeof(fsl_mcache_empty.aAge[0]));
  unsigned i;
  assert(cacheLen ==
         (unsigned)(sizeof(fsl_mcache_empty.decks)
                    /sizeof(fsl_mcache_empty.decks[0])));
  for(i=0; i<cacheLen; ++i){
    if( f->cache.mcache.decks[i].rid==rid ){
      *tgt = f->cache.mcache.decks[i];
      f->cache.mcache.decks[i] = fsl_deck_empty;
      ++f->cache.mcache.hits;
      return true;
    }
  }
  ++f->cache.mcache.misses;
  return false;
}

/**
   If mem is NULL or inside d->content.mem then this function does
   nothing, else it passes mem to fsl_free(). Intended to be used to
   clean up d->XXX string members (or sub-members) which have been
   optimized away via d->content.
*/
static void fsl_deck_free_string(fsl_deck * d, char * mem){
  assert(d);
  if(mem
     && (!d->content.used
         || !(((unsigned char const *)mem >=d->content.mem)
              &&
              ((unsigned char const *)mem < (d->content.mem+d->content.capacity)))
         )){
    fsl_free(mem);
  }/* else do nothing - the memory is NULL or owned by d->content. */
}

/**
   fsl_list_visitor_f() impl which frees fsl_list-of-(char*) card entries
   in ((fsl_deck*)visitorState).
*/
static int fsl_list_v_card_string_free(void * mCard, void * visitorState ){
  fsl_deck_free_string( (fsl_deck*)visitorState, mCard );
  return 0;
}

/** Evals to a pointer to the F-card at the given index
    in the given fsl_card_F_list pointer. Each arg is
    evaluated only once. */
#define F_at(LISTP,NDX) (&(LISTP)->list[NDX])

static int fsl_card_F_list_reserve2( fsl_card_F_list * li ){
  return (li->used<li->capacity)
    ? 0
    : fsl_card_F_list_reserve(li, li->capacity
                               ? li->capacity*4/3+1
                               : 50);
}

static void fsl_card_F_clean( fsl_card_F * f ){
  if(!f->deckOwnsStrings){
    fsl_free(f->name);
    fsl_free(f->uuid);
    fsl_free(f->priorName);
  }
  *f = fsl_card_F_empty;
}

/**
   Cleans up the F-card at li->list[ndx] and shifts all F-cards to its
   right one entry to the left.
*/
static void fsl_card_F_list_remove(fsl_card_F_list * li,
                                   uint32_t ndx){
  uint32_t i;
  assert(li->used);
  assert(ndx<li->used);
  fsl_card_F_clean(F_at(li,ndx));
  for( i = ndx; i < li->used - 1; ++i ){
    li->list[i] = li->list[i+1];
  }
  li->list[li->used] = fsl_card_F_empty;
  --li->used;
}

void fsl_card_F_list_finalize( fsl_card_F_list * li ){
  uint32_t i;
  for(i=0; i < li->used; ++i){
    fsl_card_F_clean(F_at(li,i));  
  }
  li->used = li->capacity = 0;
  fsl_free(li->list);
  *li = fsl_card_F_list_empty;
}

int fsl_card_F_list_reserve( fsl_card_F_list * li, uint32_t n ){
  if(li->capacity>=n) return 0;
  else if(n==0){
    fsl_card_F_list_finalize(li);
    return 0;
  }else{
    fsl_card_F * re = fsl_realloc(li->list, n * sizeof(fsl_card_F));
    if(re){
      li->list = re;
      li->capacity = n;
    }
    return re ? 0 : FSL_RC_OOM;
  }
}

/**
   Adjusts the end of the give list by +1, reserving more space if
   needed, and returns the next available F-card in a cleanly-wiped
   state. Returns NULL on alloc error.
*/
static fsl_card_F * fsl_card_F_list_push( fsl_card_F_list * li ){
  if(li->used==li->capacity && fsl_card_F_list_reserve2(li)) return NULL;
  li->list[li->used] = fsl_card_F_empty;
  if(li->used){
    li->flags |= FSL_CARD_F_LIST_NEEDS_SORT/*pessimistic assumption*/;
  }
  return &li->list[li->used++];
}
/**
   Chops the last entry off of the given list, freeing any resources
   owned by that entry. Decrements li->used. Asserts that li->used is
   positive.
*/
static void fsl_card_F_list_pop( fsl_card_F_list * li ){
  assert(li->used);
  if(li->used) fsl_card_F_clean(F_at(li, --li->used));
}

fsl_card_Q * fsl_card_Q_malloc(fsl_cherrypick_type_e type,
                               fsl_uuid_cstr target,
                               fsl_uuid_cstr baseline){
  int const targetLen = target ? fsl_is_uuid(target) : 0;
  int const baselineLen = baseline ? fsl_is_uuid(baseline) : 0;
  if((type!=FSL_CHERRYPICK_ADD && type!=FSL_CHERRYPICK_BACKOUT)
     || !target || !targetLen
     || (baseline && !baselineLen)) return NULL;
  else{
    fsl_card_Q * c =
      (fsl_card_Q*)fsl_malloc(sizeof(fsl_card_Q));
    if(c){
      int rc = 0;
      *c = fsl_card_Q_empty;
      c->type = type;
      c->target = fsl_strndup(target, targetLen);
      if(!c->target) rc = FSL_RC_OOM;
      else if(baseline){
        c->baseline = fsl_strndup(baseline, baselineLen);
        if(!c->baseline) rc = FSL_RC_OOM;
      }
      if(rc){
        fsl_card_Q_free(c);
        c = NULL;
      }
    }
    return c;
  }
}

void fsl_card_Q_free( fsl_card_Q * cp ){
  if(cp){
    fsl_free(cp->target);
    fsl_free(cp->baseline);
    *cp = fsl_card_Q_empty;
    fsl_free(cp);
  }
}

fsl_card_J * fsl_card_J_malloc(bool isAppend,
                               char const * field,
                               char const * value){
  if(!field || !*field) return NULL;
  else{
    fsl_card_J * c =
      (fsl_card_J*)fsl_malloc(sizeof(fsl_card_J));
    if(c){
      int rc = 0;
      fsl_size_t const lF = fsl_strlen(field);
      fsl_size_t const lV = value ? fsl_strlen(value) : 0;
      *c = fsl_card_J_empty;
      c->append = isAppend ? 1 : 0;
      c->field = fsl_strndup(field, (fsl_int_t)lF);
      if(!c->field) rc = FSL_RC_OOM;
      else if(value && *value){
        c->value = fsl_strndup(value, (fsl_int_t)lV);
        if(!c->value) rc = FSL_RC_OOM;
      }
      if(rc){
        fsl_card_J_free(c);
        c = NULL;
      }
    }
    return c;
  }
}

void fsl_card_J_free( fsl_card_J * cp ){
  if(cp){
    fsl_free(cp->field);
    fsl_free(cp->value);
    *cp = fsl_card_J_empty;
    fsl_free(cp);
  }
}

/**
    fsl_list_visitor_f() impl which requires that obj be-a (fsl_card_T*),
    which this function passes to fsl_card_T_free().
*/
static int fsl_list_v_card_T_free(void * obj, void * visitorState ){
  if(obj) fsl_card_T_free( (fsl_card_T*)obj );
  return 0;
}

static int fsl_list_v_card_Q_free(void * obj, void * visitorState ){
  if(obj) fsl_card_Q_free( (fsl_card_Q*)obj );
  return 0;
}

static int fsl_list_v_card_J_free(void * obj, void * visitorState ){
  if(obj) fsl_card_J_free( (fsl_card_J*)obj );
  return 0;
}

fsl_deck * fsl_deck_malloc(){
  fsl_deck * rc = (fsl_deck *)fsl_malloc(sizeof(fsl_deck));
  if(rc){
    *rc = fsl_deck_empty;
    rc->allocStamp = &fsl_deck_empty;
  }
  return rc;
}

void fsl_deck_init( fsl_cx * f, fsl_deck * cards, fsl_satype_e type ){
  void const * allocStamp = cards->allocStamp;
  *cards = fsl_deck_empty;
  cards->allocStamp = allocStamp;
  cards->f = f;
  cards->type = type;
}

void fsl_card_J_list_free(fsl_list * li, bool alsoListMem){
  if(li->used) fsl_list_visit(li, 0, fsl_list_v_card_J_free, NULL);
  if(alsoListMem) fsl_list_reserve(li, 0);
  else li->used = 0;
}

/* fsl_deck cleanup helpers... */
#define SFREE(X) fsl_deck_clean_string(m, &m->X)
#define SLIST(X) fsl_list_clear(&m->X, fsl_list_v_card_string_free, m)
#define CBUF(X) fsl_buffer_clear(&m->X)
static void fsl_deck_clean_string(fsl_deck *m, char **member){
  fsl_deck_free_string(m, *member);
  *member = 0;
}
static void fsl_deck_clean_version(fsl_deck *m){
  fsl_deck_clean_string(m, &m->uuid);
  m->rid = 0;
}
static void fsl_deck_clean_A(fsl_deck *m){
  SFREE(A.name);
  SFREE(A.tgt);
  SFREE(A.src);
}
static void fsl_deck_clean_B(fsl_deck *m){
  if(m->B.baseline){
    assert(!m->B.baseline->B.uuid && "Baselines cannot have a B-card. API misuse?");
    fsl_deck_finalize(m->B.baseline);
    m->B.baseline = NULL;
  }
  SFREE(B.uuid);
}
static void fsl_deck_clean_C(fsl_deck *m){
  fsl_deck_clean_string(m, &m->C);
}
static void fsl_deck_clean_E(fsl_deck *m){
  fsl_deck_clean_string(m, &m->E.uuid);
  m->E = fsl_deck_empty.E;
}
static void fsl_deck_clean_F(fsl_deck *m){
  if(m->F.list){
    fsl_card_F_list_finalize(&m->F);
    m->F = fsl_deck_empty.F;
  }
}
static void fsl_deck_clean_G(fsl_deck *m){
  fsl_deck_clean_string(m, &m->G);
}
static void fsl_deck_clean_H(fsl_deck *m){
  fsl_deck_clean_string(m, &m->H);
}
static void fsl_deck_clean_I(fsl_deck *m){
  fsl_deck_clean_string(m, &m->I);
}
static void fsl_deck_clean_J(fsl_deck *m, bool alsoListMem){
  fsl_card_J_list_free(&m->J, alsoListMem);
}
static void fsl_deck_clean_K(fsl_deck *m){
  fsl_deck_clean_string(m, &m->K);
}
static void fsl_deck_clean_L(fsl_deck *m){
  fsl_deck_clean_string(m, &m->L);
}
static void fsl_deck_clean_M(fsl_deck *m){
  SLIST(M);
}
static void fsl_deck_clean_N(fsl_deck *m){
  fsl_deck_clean_string(m, &m->N);
}
static void fsl_deck_clean_P(fsl_deck *m){
  fsl_list_clear(&m->P, fsl_list_v_card_string_free, m);
}
static void fsl_deck_clean_Q(fsl_deck *m){
  fsl_list_clear(&m->Q, fsl_list_v_card_Q_free, NULL);
}
static void fsl_deck_clean_R(fsl_deck *m){
  fsl_deck_clean_string(m, &m->R);
}
static void fsl_deck_clean_T(fsl_deck *m){
  fsl_list_clear(&m->T, fsl_list_v_card_T_free, NULL);
}
static void fsl_deck_clean_U(fsl_deck *m){
  fsl_deck_clean_string(m, &m->U);
}
static void fsl_deck_clean_W(fsl_deck *m){
  CBUF(W);
}

void fsl_deck_clean2(fsl_deck *m, fsl_buffer *xferBuf){
  if(!m) return;
  fsl_deck_clean_version(m);  
  fsl_deck_clean_A(m);
  fsl_deck_clean_B(m);
  fsl_deck_clean_C(m);
  m->D = 0.0;
  fsl_deck_clean_E(m);
  fsl_deck_clean_F(m);
  fsl_deck_clean_G(m);
  fsl_deck_clean_H(m);
  fsl_deck_clean_I(m);
  fsl_deck_clean_J(m,true);
  fsl_deck_clean_K(m);
  fsl_deck_clean_L(m);
  fsl_deck_clean_M(m);
  fsl_deck_clean_N(m);
  fsl_deck_clean_P(m);
  fsl_deck_clean_Q(m);
  fsl_deck_clean_R(m);
  fsl_deck_clean_T(m);
  fsl_deck_clean_U(m);
  fsl_deck_clean_W(m);
  if(xferBuf){
    fsl_buffer_swap(&m->content, xferBuf);
    fsl_buffer_reuse(xferBuf);
  }
  CBUF(content) /* content must be after all cards because some point
                   back into it and we need this memory intact in
                   order to know that!
                */;
  {
    void const * const allocStampKludge = m->allocStamp;
    fsl_cx * const f = m->f;
    *m = fsl_deck_empty;
    m->allocStamp = allocStampKludge;
    m->f = f;
  }
}
#undef CBUF
#undef SFREE
#undef SLIST

void fsl_deck_clean(fsl_deck *m){
  fsl_deck_clean2(m, NULL);
}

void fsl_deck_finalize(fsl_deck *m){
  void const * allocStamp;
  if(!m) return;
  allocStamp = m->allocStamp;
  fsl_deck_clean(m);
  if(allocStamp == &fsl_deck_empty){
    fsl_free(m);
  }else{
    m->allocStamp = allocStamp;
  }
}

int fsl_card_is_legal( fsl_satype_e t, char card ){
  /*
    Implements this table:
    
    https://fossil-scm.org/index.html/doc/trunk/www/fileformat.wiki#summary
  */
  if('Z'==card) return 1;
  else switch(t){
    case FSL_SATYPE_ANY:
      switch(card){
        case 'A': case 'B': case 'C': case 'D':
        case 'E': case 'F': case 'J': case 'K':
        case 'L': case 'M': case 'N': case 'P':
        case 'Q': case 'R': case 'T': case 'U':
        case 'W':
          return -1;
        default:
          return 0;
      }
    case FSL_SATYPE_ATTACHMENT:
      switch(card){
        case 'A': case 'D':
          return 1;
        case 'C': case 'N': case 'U':
          return -1;
        default:
          return 0;
      };
    case FSL_SATYPE_CLUSTER:
      return 'M'==card ? 1 : 0;
    case FSL_SATYPE_CONTROL:
      switch(card){
        case 'D': case 'T': case 'U':
          return 1;
        default:
          return 0;
      };
    case FSL_SATYPE_EVENT:
      switch(card){
        case 'D': case 'E':
        case 'W':
          return 1;
        case 'C': case 'N':
        case 'P': case 'T':
        case 'U':
          return -1;
        default:
          return 0;
      };
    case FSL_SATYPE_CHECKIN:
      switch(card){
        case 'C': case 'D':
        case 'U':
          return 1;
        case 'B': case 'F': 
        case 'N': case 'P':
        case 'Q': case 'R':
        case 'T': 
          return -1;
        default:
          return 0;
      };
    case FSL_SATYPE_TICKET:
      switch(card){
        case 'D': case 'J':
        case 'K': case 'U':
          return 1;
        default:
          return 0;
      };
    case FSL_SATYPE_WIKI:
      switch(card){
        case 'D': case 'L':
        case 'U': case 'W':
          return 1;
        case 'C':
        case 'N': case 'P':
          return -1;
        default:
          return 0;
      };
    case FSL_SATYPE_FORUMPOST:
      switch(card){
        case 'D': case 'U': case 'W':
          return 1;
        case 'G': case 'H': case 'I':
        case 'N': case 'P':
          return -1;
        default:
          return 0;
      };
    default:
      assert(!"Invalid fsl_satype_e.");
      return 0;
  };
}

bool fsl_deck_has_required_cards( fsl_deck const * d ){
  if(!d) return 0;
  switch(d->type){
    case FSL_SATYPE_ANY:
      return 0;
#define NEED(CARD,COND) \
      if(!(COND)) {                                         \
        fsl_cx_err_set(d->f, FSL_RC_SYNTAX,                 \
                       "Required %c-card is missing or invalid.", \
                       *#CARD);                                   \
        return false;                                             \
      } (void)0
    case FSL_SATYPE_ATTACHMENT:
      NEED(A,d->A.name);
      NEED(A,d->A.tgt);
      NEED(D,d->D > 0);
      return 1;
    case FSL_SATYPE_CLUSTER:
      NEED(M,d->M.used);
      return 1;
    case FSL_SATYPE_CONTROL:
      NEED(D,d->D > 0);
      NEED(U,d->U);
      NEED(T,d->T.used>0);
      return 1;
    case FSL_SATYPE_EVENT:
      NEED(D,d->D > 0);
      NEED(E,d->E.julian>0);
      NEED(E,d->E.uuid);
      NEED(W,d->W.used);
      return 1;
    case FSL_SATYPE_CHECKIN:
      /*
        Historically we need both or neither of F- and R-cards, but
        the R-card has become optional because it's so expensive to
        calculate and verify.

        Manifest #1 has an empty file list and an R-card with a
        constant (repo/manifest-independent) hash
        (d41d8cd98f00b204e9800998ecf8427e, the initial MD5 hash
        state).

        R-card calculation is runtime-configurable option. Rather
        than rely on d->f being set (so d->f->flags can tell us
        whether or not to calculate an R-card), we'll rely on
        downstream code to check for an R-card if needed.
      */
      NEED(D,d->D > 0);
      NEED(C,d->C);
      NEED(U,d->U);
#if 0
      /* It turns out that because the R-card is optional,
         we can have a legal manifest with no F-cards. */
      NEED(F,d->F.used || d->R/*with initial-state md5 hash!*/);
#endif
      if(!d->R
         && (FSL_CX_F_CALC_R_CARD & d->f->flags)){
        fsl_cx_err_set(d->f, FSL_RC_SYNTAX,
                       "%s deck is missing an R-card, "
                       "yet R-card calculation is enabled.",
                       fsl_satype_cstr(d->type));
        return 0;
      }else if(d->R
               && !d->F.used
               && 0!=fsl_strcmp(d->R, FSL_MD5_INITIAL_HASH)
               ){
        fsl_cx_err_set(d->f, FSL_RC_SYNTAX,
                       "Deck has no F-cards, so we expect its "
                       "R-card is to have the initial-state MD5 "
                       "hash (%.12s...). Instead we got: %s",
                       FSL_MD5_INITIAL_HASH, d->R);
        return 0;
      }
      return 1;
    case FSL_SATYPE_TICKET:
      NEED(D,d->D > 0);
      NEED(K,d->K);
      NEED(U,d->U);
      NEED(J,d->J.used)
        /* Is a J strictly required?  Spec is not clear but DRH
           confirms the current fossil(1) code expects a J card. */;
      return 1;
    case FSL_SATYPE_WIKI:
      NEED(D,d->D > 0);
      NEED(L,d->L);
      NEED(U,d->U);
      /*NEED(W,d->W.used);*/
      return 1;
    case FSL_SATYPE_FORUMPOST:
      NEED(D,d->D > 0);
      NEED(U,d->U);
      /*NEED(W,d->W.used);*/
      return 1;
    case FSL_SATYPE_INVALID:
    default:
      assert(!"Invalid fsl_satype_e.");
      return 0;
  }
#undef NEED
}


char const * fsl_satype_cstr(fsl_satype_e t){
  switch(t){
#define C(X) case FSL_SATYPE_##X: return #X
    C(ANY);
    C(CHECKIN);
    C(CLUSTER);
    C(CONTROL);
    C(WIKI);
    C(TICKET);
    C(ATTACHMENT);
    C(EVENT);
    C(FORUMPOST);
    C(INVALID);
    C(BRANCH_START);
    default:
      assert(!"UNHANDLED fsl_satype_e");
      return "!UNKNOWN!";
  }
}

char const * fsl_satype_event_cstr(fsl_satype_e t){
  switch(t){
    case FSL_SATYPE_ANY: return "*";
    case FSL_SATYPE_BRANCH_START:
    case FSL_SATYPE_CHECKIN: return "ci";
    case FSL_SATYPE_EVENT: return "e";
    case FSL_SATYPE_CONTROL: return "g";
    case FSL_SATYPE_TICKET: return "t";
    case FSL_SATYPE_WIKI: return "w";
    case FSL_SATYPE_FORUMPOST: return "f";
    default:
      return NULL;
  }
}


/**
    If fsl_card_is_legal(d->type, card), returns true, else updates
    d->f->error with a description of the constraint violation and
    returns 0.
 */
static bool fsl_deck_check_type( fsl_deck * d, char card ){
  if(fsl_card_is_legal(d->type, card)) return true;
  else{
    fsl_cx_err_set(d->f, FSL_RC_TYPE,
                   "Card type '%c' is not allowed "
                   "in artifacts of type %s.",
                   card, fsl_satype_cstr(d->type));
    return false;
  }
}

/**
   If the first n bytes of the given string contain any values <=32,
   returns FSL_RC_SYNTAX, else returns 0. mf->f's error state is
   updated no error. n<0 means to use fsl_strlen() to count the
   length.
*/
static int fsl_deck_strcheck_ctrl_chars(fsl_deck * mf, char cardName, char const * v, fsl_int_t n){
  const char * z = v;
  int rc = 0;
  if(v && n<0) n = fsl_strlen(v);
  for( ; v && z < v+n; ++z ){
    if(*z <= 32){
      rc = fsl_cx_err_set(mf->f, FSL_RC_SYNTAX,
                         "Invalid character in %c-card.", cardName);
      break;
    }
  }
  return rc;
}

/*
  Implements fsl_deck_LETTER_set() for certain letters: those
  implemented as a fsl_uuid_str or an md5, holding a single hex string
  value.
  
  The function returns FSL_RC_SYNTAX if
  (valLen!=ASSERTLEN). ASSERTLEN is assumed to be either an SHA1,
  SHA3, or MD5 hash value and it is validated against
  fsl_validate16(value,valLen), returning FSL_RC_SYNTAX if that
  check fails. In debug builds, the expected ranges are assert()ed.

  If value is NULL then it is removed from the card instead
  (semantically freed), *mfMember is set to NULL, and 0 is returned.
*/
static int fsl_deck_sethex_impl( fsl_deck * mf, fsl_uuid_cstr value,
                                 char letter,
                                 fsl_size_t assertLen,
                                 char ** mfMember ){
  assert(mf);
  assert( value ? (assertLen==FSL_STRLEN_SHA1
                   || assertLen==FSL_STRLEN_K256
                   || assertLen==FSL_STRLEN_MD5)
          : 0==assertLen );
  if(value && !fsl_deck_check_type(mf,letter)) return mf->f->error.code;
  else if(!value){
    fsl_deck_free_string(mf, *mfMember);
    *mfMember = NULL;
    return 0;
  }else if(fsl_strlen(value) != assertLen){
    return fsl_cx_err_set(mf->f, FSL_RC_SYNTAX,
                          "Invalid length for %c-card: expecting %d.",
                          letter, (int)assertLen);
  }else if(!fsl_validate16(value, assertLen)) {
    return fsl_cx_err_set(mf->f, FSL_RC_SYNTAX,
                          "Invalid hexadecimal value for %c-card.", letter);
  }else{
    fsl_deck_free_string(mf, *mfMember);
    *mfMember = fsl_strndup(value, assertLen);
    return *mfMember ? 0 : FSL_RC_OOM;
  }
}

/**
    Implements fsl_set_set_XXX() where XXX is a fsl_buffer member of fsl_deck.
 */
static int fsl_deck_b_setuffer_impl( fsl_deck * mf, char const * value,
                                     fsl_int_t valLen,
                                     char letter, fsl_buffer * buf){
  assert(mf);  
  if(!fsl_deck_check_type(mf,letter)) return mf->f->error.code;
  else if(valLen<0) valLen = (fsl_int_t)fsl_strlen(value);
  buf->used = 0;
  if(value && (valLen>0)){
    return fsl_buffer_append( buf, value, valLen );
  }else{
    if(buf->mem) buf->mem[0] = 0;
    return 0;
  }
}

int fsl_deck_B_set( fsl_deck * mf, fsl_uuid_cstr uuidBaseline){
  if(!mf) return FSL_RC_MISUSE;
  else{
    int const bLen = uuidBaseline ? fsl_is_uuid(uuidBaseline) : 0;
    if(uuidBaseline && !bLen){
      return fsl_cx_err_set(mf->f, FSL_RC_SYNTAX,
                            "Invalid B-card value: %s", uuidBaseline);
    }
    if(mf->B.baseline){
      fsl_deck_finalize(mf->B.baseline);
      mf->B.baseline = NULL;
    }
    return fsl_deck_sethex_impl(mf, uuidBaseline, 'B',
                                bLen, &mf->B.uuid);
  }
}

/**
    Internal impl for card setters which consist of a simple (char *)
    member. Replaces and frees any prior value. Passing NULL for the
    4th argument unsets the given card (assigns NULL to it).
 */
static int fsl_deck_set_string( fsl_deck * mf, char letter, char ** member, char const * v, fsl_int_t n ){
  if(!fsl_deck_check_type(mf, letter)) return mf->f->error.code;
  fsl_deck_free_string(mf, *member);
  *member = v ? fsl_strndup(v, n) : NULL;
  if(v && !*member) return FSL_RC_OOM;
  else return 0;
}

int fsl_deck_C_set( fsl_deck * mf, char const * v, fsl_int_t n){
  return fsl_deck_set_string( mf, 'C', &mf->C, v, n );
}

int fsl_deck_G_set( fsl_deck * mf, fsl_uuid_cstr uuid){
  int const uLen = fsl_is_uuid(uuid);
  return uLen
    ? fsl_deck_sethex_impl(mf, uuid, 'G', uLen, &mf->G)
    : FSL_RC_SYNTAX;
}

int fsl_deck_H_set( fsl_deck * mf, char const * v, fsl_int_t n){
  if(v && mf->I) return FSL_RC_SYNTAX;
  return fsl_deck_set_string( mf, 'H', &mf->H, v, n );
}

int fsl_deck_I_set( fsl_deck * mf, fsl_uuid_cstr uuid){
  if(uuid && mf->H) return FSL_RC_SYNTAX;
  int const uLen = uuid ? fsl_is_uuid(uuid) : 0;
  return fsl_deck_sethex_impl(mf, uuid, 'I', uLen, &mf->I);
}

int fsl_deck_J_add( fsl_deck * mf, char isAppend,
                    char const * field, char const * value){
  if(!field) return FSL_RC_MISUSE;
  else if(!*field) return FSL_RC_SYNTAX;
  else if(!fsl_deck_check_type(mf,'J')) return mf->f->error.code;
  else{
    int rc;
    fsl_card_J * cp = fsl_card_J_malloc(isAppend, field, value);
    if(!cp) rc = FSL_RC_OOM;
    else if( 0 != (rc = fsl_list_append(&mf->J, cp))){
      fsl_card_J_free(cp);
    }
    return rc;
  }
}


int fsl_deck_K_set( fsl_deck * mf, fsl_uuid_cstr uuid){
  int const uLen = fsl_is_uuid(uuid);
  return uLen
    ? fsl_deck_sethex_impl(mf, uuid, 'K', uLen, &mf->K)
    : FSL_RC_SYNTAX;
}

int fsl_deck_L_set( fsl_deck * mf, char const * v, fsl_int_t n){
  return mf
    ? fsl_deck_set_string(mf, 'L', &mf->L, v, n)
    : FSL_RC_SYNTAX;
}

int fsl_deck_M_add( fsl_deck * mf, char const *uuid){
  int rc;
  char * dupe;
  int const uLen = uuid ? fsl_is_uuid(uuid) : 0;
  if(!uuid) return FSL_RC_MISUSE;
  else if(!fsl_deck_check_type(mf, 'M')) return mf->f->error.code;
  else if(!uLen) return FSL_RC_SYNTAX;
  dupe = fsl_strndup(uuid, uLen);
  if(!dupe) rc = FSL_RC_OOM;
  else{
    rc = fsl_list_append( &mf->M, dupe );
    if(rc){
      fsl_free(dupe);
    }
  }
  return rc;
}

int fsl_deck_N_set( fsl_deck * mf, char const * v, fsl_int_t n){
  int rc = 0;
  if(v && n!=0){
    if(n<0) n = fsl_strlen(v);
    rc = fsl_deck_strcheck_ctrl_chars(mf, 'N', v, n);
  }
  return rc ? rc : fsl_deck_set_string( mf, 'N', &mf->N, v, n );
}


int fsl_deck_P_add( fsl_deck * mf, char const *parentUuid){
  int rc;
  char * dupe;
  int const uLen = parentUuid ? fsl_is_uuid(parentUuid) : 0;
  if(!mf || !parentUuid || !*parentUuid) return FSL_RC_MISUSE;
  else if(!fsl_deck_check_type(mf, 'P')) return mf->f->error.code;
  else if(!uLen){
    return fsl_cx_err_set(mf->f, FSL_RC_SYNTAX,
                         "Invalid UUID for P-card.");
  }
  dupe = fsl_strndup(parentUuid, uLen);
  if(!dupe) rc = FSL_RC_OOM;
  else{
    rc = fsl_list_append( &mf->P, dupe );
    if(rc){
      fsl_free(dupe);
    }
  }
  return rc;
}

fsl_id_t fsl_deck_P_get_id(fsl_deck * d, int index){
  if(!d->f) return -1;
  else if(index>(int)d->P.used) return 0;
  else return fsl_uuid_to_rid(d->f, (char const *)d->P.list[index]);
}


int fsl_deck_Q_add( fsl_deck * mf, int type,
                    fsl_uuid_cstr target,
                    fsl_uuid_cstr baseline ){
  if(!target) return FSL_RC_MISUSE;
  else if(!fsl_deck_check_type(mf,'Q')) return mf->f->error.code;
  else if(!type || !fsl_is_uuid(target)
          || (baseline && !fsl_is_uuid(baseline))) return FSL_RC_SYNTAX;
  else{
    int rc;
    fsl_card_Q * cp = fsl_card_Q_malloc(type, target, baseline);
    if(!cp) rc = FSL_RC_OOM;
    else if( 0 != (rc = fsl_list_append(&mf->Q, cp))){
      fsl_card_Q_free(cp);
    }
    return rc;
  }
}

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

    It expects that each argument is a (fsl_card_F const *).
*/
static int fsl_card_F_cmp( void const * lhs, void const * rhs ){
  fsl_card_F const * const l = (fsl_card_F const *)lhs;
  fsl_card_F const * const r = (fsl_card_F const *)rhs;
  /* Compare NULL as larger so that NULLs move to the right. That said,
     we aren't expecting any NULLs. */
  assert(l);
  assert(r);
  if(!l) return r ? 1 : 0;
  else if(!r) return -1;
  else return fsl_strcmp(l->name, r->name);
}

static void fsl_card_F_list_sort(fsl_card_F_list * li){
  if(FSL_CARD_F_LIST_NEEDS_SORT & li->flags){
    qsort(li->list, li->used, sizeof(fsl_card_F),
          fsl_card_F_cmp );
    li->flags &= ~FSL_CARD_F_LIST_NEEDS_SORT;
  }
}

static void fsl_deck_F_sort(fsl_deck * mf){
  fsl_card_F_list_sort(&mf->F);
}

int fsl_card_F_compare( fsl_card_F const * lhs,
                        fsl_card_F const * rhs){
  return (lhs == rhs) ? 0 : fsl_card_F_cmp( lhs, rhs );
}

int fsl_deck_R_set( fsl_deck * mf, fsl_uuid_cstr md5){
  return mf
    ? fsl_deck_sethex_impl(mf, md5, 'R', md5 ? FSL_STRLEN_MD5 : 0, &mf->R)
    : FSL_RC_MISUSE;
}

int fsl_deck_R_calc2(fsl_deck *mf, char ** tgt){
  fsl_cx * f = mf->f;
  char const * theHash = 0;
  char hex[FSL_STRLEN_MD5+1];
  if(!f) return FSL_RC_MISUSE;
  else if(!fsl_needs_repo(f)){
    return FSL_RC_NOT_A_REPO;
  }else if(!fsl_deck_check_type(mf,'R')) {
    assert(mf->f->error.code);
    return mf->f->error.code;
  }else if(!mf->F.used){
    theHash = FSL_MD5_INITIAL_HASH;
    /* fall through and set hash */
  }else{
    int rc = 0;
    fsl_card_F const * fc;
    fsl_id_t fileRid;
    fsl_buffer * buf = &f->fileContent;
    unsigned char digest[16];
    fsl_md5_cx md5 = fsl_md5_cx_empty;
    enum { NumBufSize = 40 };
    char numBuf[NumBufSize] = {0};
    assert(!buf->used && "Misuse of f->fileContent buffer.");
    rc = fsl_deck_F_rewind(mf);
    if(rc) goto end;
    fsl_deck_F_sort(mf);
    /*
      TODO:

      Missing functionality:

      - The "wd" (working directory) family of functions, needed for
      symlink handling.
    */
    while(1){
      rc = fsl_deck_F_next(mf, &fc);
      /* MARKER(("R rc=%s file #%d: %s %s\n", fsl_rc_cstr(rc), ++i, fc ? fc->name : "<END>", fc ? fc->uuid : NULL)); */
      if(rc || !fc) break;
      assert(fc->uuid && "We no longer iterate over deleted entries.");
      if(FSL_FILE_PERM_LINK==fc->perm){
        rc = fsl_cx_err_set(f, FSL_RC_UNSUPPORTED,
                            "This code does not yet properly handle "
                            "F-cards of symlinks.");
        goto end;
      }
      fileRid = fsl_uuid_to_rid( f, fc->uuid );
      if(0==fileRid){
        rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                            "Cannot resolve RID for F-card UUID [%s].",
                            fc->uuid);
        goto end;
      }else if(fileRid<0){
        assert(f->error.code);
        rc = f->error.code
          ? f->error.code
          : fsl_cx_err_set(f, FSL_RC_ERROR,
                           "Error resolving RID for F-card UUID [%s].",
                           fc->uuid);
        goto end;
      }
      fsl_md5_update_cstr(&md5, fc->name, -1);
      rc = fsl_content_get(f, fileRid, buf);
      if(rc){
        goto end;
      }
      numBuf[0] = 0;
      fsl_snprintf(numBuf, NumBufSize,
                   " %"FSL_SIZE_T_PFMT"\n",
                   (fsl_size_t)buf->used);
      fsl_md5_update_cstr(&md5, numBuf, -1);
      fsl_md5_update_buffer(&md5, buf);
    }
    if(!rc){
      fsl_md5_final(&md5, digest);
      fsl_md5_digest_to_base16(digest, hex);
    }
    end:
    fsl_cx_content_buffer_yield(f);
    assert(0==buf->used);
    if(rc) return rc;
    fsl_deck_F_rewind(mf);
    theHash = hex;
  }
  assert(theHash);
  if(*tgt){
    memcpy(*tgt, theHash, FSL_STRLEN_MD5);
    (*tgt)[FSL_STRLEN_MD5+1] = 0;
    return 0;
  }else{
    char * x = fsl_strdup(theHash);
    if(x) *tgt = x;
    return x ? 0 : FSL_RC_OOM;
  }
}

int fsl_deck_R_calc(fsl_deck * mf){
  char R[FSL_STRLEN_MD5+1] = {0};
  char * r = R;
  int rc = fsl_deck_R_calc2(mf, &r);
  return rc ? rc : fsl_deck_R_set(mf, r);
}

int fsl_deck_T_add2( fsl_deck * mf, fsl_card_T * t){
  if(!t) return FSL_RC_MISUSE;
  else if(!fsl_deck_check_type(mf, 'T')){
    return mf->f->error.code;
  }else if(FSL_SATYPE_CONTROL==mf->type && NULL==t->uuid){
    return fsl_cx_err_set(mf->f, FSL_RC_SYNTAX,
                            "CONTROL artifacts may not have "
                            "self-referential tags.");
  }else if(FSL_SATYPE_TECHNOTE==mf->type){
    if(NULL!=t->uuid){
      return fsl_cx_err_set(mf->f, FSL_RC_SYNTAX,
                              "TECHNOTE artifacts may not have "
                              "tags which refer to other objects.");
    }else if(FSL_TAGTYPE_ADD!=t->type){
      return fsl_cx_err_set(mf->f, FSL_RC_SYNTAX,
                            "TECHNOTE artifacts may only have "
                            "ADD-type tags.");
    }
  }
  if(!t->name || !*t->name){
    return fsl_cx_err_set(mf->f, FSL_RC_SYNTAX,
                         "Tag name may not be empty.");
  }else if(fsl_validate16(t->name, fsl_strlen(t->name))){
    return fsl_cx_err_set(mf->f, FSL_RC_SYNTAX,
                         "Tag name may not be hexadecimal.");
  }else if(t->uuid && !fsl_is_uuid(t->uuid)){
    return fsl_cx_err_set(mf->f, FSL_RC_SYNTAX,
                         "Invalid UUID in tag.");
  }
  return fsl_list_append(&mf->T, t);
}

int fsl_deck_T_add( fsl_deck * mf, fsl_tagtype_e tagType,
                    char const * uuid, char const * name,
                    char const * value){
  if(!name) return FSL_RC_MISUSE;
  else if(!fsl_deck_check_type(mf, 'T')) return mf->f->error.code;
  else if(!*name || (uuid &&!fsl_is_uuid(uuid))) return FSL_RC_SYNTAX;
  else switch(tagType){
    case FSL_TAGTYPE_CANCEL:
    case FSL_TAGTYPE_ADD:
    case FSL_TAGTYPE_PROPAGATING:{
      int rc;
      fsl_card_T * t;
      t = fsl_card_T_malloc(tagType, uuid, name, value);
      if(!t) return FSL_RC_OOM;
      rc = fsl_deck_T_add2( mf, t );
      if(rc) fsl_card_T_free(t);
      return rc;
    }
    default:
      assert(!"Invalid tagType value");
      return fsl_cx_err_set(mf->f, FSL_RC_SYNTAX,
                            "Invalid tag-type value: %d",
                            (int)tagType);
  }
}

/**
   Returns true if the NUL-terminated string contains only
   "reasonable" branch name character, with the native assumption that
   anything <=32d is "unreasonable" and anything >=128 is part of a
   multibyte UTF8 character.
*/
static bool fsl_is_valid_branchname(char const * z_){
  unsigned char const * z = (unsigned char const*)z_;
  unsigned len = 0;
  for(; z[len]; ++len){
    if(z[len] <= 32) return false;
  }
  return len>0;
}

int fsl_deck_branch_set( fsl_deck * d, char const * branchName ){
  if(!fsl_is_valid_branchname(branchName)){
    return fsl_cx_err_set(d->f, FSL_RC_RANGE, "Branch name contains "
                          "invalid characters.");
  }
  int rc= fsl_deck_T_add(d, FSL_TAGTYPE_PROPAGATING, NULL,
                         "branch", branchName);
  if(!rc){
    char * sym = fsl_mprintf("sym-%s", branchName);
    if(sym){
      rc = fsl_deck_T_add(d, FSL_TAGTYPE_PROPAGATING, NULL,
                          sym, NULL);
      fsl_free(sym);
    }else{
      rc = FSL_RC_OOM;
    }
  }
  return rc;
}

int fsl_deck_U_set( fsl_deck * mf, char const * v){
  return fsl_deck_set_string( mf, 'U', &mf->U, v, -1 );
}

int fsl_deck_W_set( fsl_deck * mf, char const * v, fsl_int_t n){
  return fsl_deck_b_setuffer_impl(mf, v, n, 'W', &mf->W);
}

int fsl_deck_A_set( fsl_deck * mf, char const * name,
                    char const * tgt,
                    char const * uuidSrc ){
  int const uLen = (uuidSrc && *uuidSrc) ? fsl_is_uuid(uuidSrc) : 0;
  if(!name || !tgt) return FSL_RC_MISUSE;
  else if(!fsl_deck_check_type(mf, 'A')) return mf->f->error.code;
  else if(!*tgt){
    return fsl_cx_err_set(mf->f, FSL_RC_SYNTAX,
                          "Invalid target name in A card.");
  }
  /* TODO: validate tgt based on mf->type and require UUID
     for types EVENT/TICKET.
  */
  else if(uuidSrc && *uuidSrc && !uLen){
    return fsl_cx_err_set(mf->f, FSL_RC_SYNTAX,
                          "Invalid source UUID in A card.");
  }
  else{
    int rc = 0;
    fsl_deck_free_string(mf, mf->A.tgt);
    fsl_deck_free_string(mf, mf->A.src);
    fsl_deck_free_string(mf, mf->A.name);
    mf->A.name = mf->A.src = NULL;
    if(! (mf->A.tgt = fsl_strdup(tgt))) rc = FSL_RC_OOM;
    else if( !(mf->A.name = fsl_strdup(name))) rc = FSL_RC_OOM;
    else if(uLen){
      mf->A.src = fsl_strndup(uuidSrc,uLen);
      if(!mf->A.src) rc = FSL_RC_OOM
        /* Leave mf->A.tgt/name for downstream cleanup. */;
    }
    return rc;
  }
}


int fsl_deck_D_set( fsl_deck * mf, double date){
  if(date<0) return FSL_RC_RANGE;
  else if(date>0 && !fsl_deck_check_type(mf, 'D')){
    return mf->f->error.code;
  }else{
    mf->D = date;
    return 0;
  }
}

int fsl_deck_E_set( fsl_deck * mf, double date, char const * uuid){
  int const uLen = uuid ? fsl_is_uuid(uuid) : 0;
  if(!mf || !uLen) return FSL_RC_MISUSE;
  else if(date<=0){
    return fsl_cx_err_set(mf->f, FSL_RC_RANGE,
                          "Invalid date value for E card.");
  }else if(!uLen){
    return fsl_cx_err_set(mf->f, FSL_RC_RANGE,
                          "Invalid UUID for E card.");
  }
  else{
    mf->E.julian = date;
    fsl_deck_free_string(mf, mf->E.uuid);
    mf->E.uuid = fsl_strndup(uuid, uLen);
    return mf->E.uuid ? 0 : FSL_RC_OOM;
  }
}

int fsl_deck_F_add( fsl_deck * mf, char const * name,
                    char const * uuid,
                    fsl_fileperm_e perms, 
                    char const * oldName){
  int const uLen = uuid ? fsl_is_uuid(uuid) : 0;
  if(!mf || !name) return FSL_RC_MISUSE;
  else if(!uuid && !mf->B.uuid){
    return fsl_cx_err_set(mf->f, FSL_RC_MISUSE,
                         "NULL UUID is not valid for baseline "
                          "manifests.");
  }
  else if(!fsl_deck_check_type(mf, 'F')) return mf->f->error.code;
  else if(!*name){
    return fsl_cx_err_set(mf->f, FSL_RC_RANGE,
                         "F-card name may not be empty.");
  }
  else if(!fsl_is_simple_pathname(name, 1)
          || (oldName && !fsl_is_simple_pathname(oldName, 1))){
    return fsl_cx_err_set(mf->f, FSL_RC_RANGE,
                          "Invalid filename for F-card (simple form required): "
                          "name=[%s], oldName=[%s].", name, oldName);
  }
  else if(uuid && !uLen){
    return fsl_cx_err_set(mf->f, FSL_RC_RANGE,
                          "Invalid UUID for F-card.");
  }
  else {
    int rc = 0;
    fsl_card_F * t;
    switch(perms){
      case FSL_FILE_PERM_EXE:
      case FSL_FILE_PERM_LINK:
      case FSL_FILE_PERM_REGULAR:
        break;
      default:
        assert(!"Invalid fsl_fileperm_e value");
        return fsl_cx_err_set(mf->f, FSL_RC_RANGE,
                              "Invalid fsl_fileperm_e value "
                              "(%d) for file [%s].",
                              perms, name);
    }
    t = fsl_card_F_list_push(&mf->F);
    if(!t) return FSL_RC_OOM;
    assert(mf->F.used>1
           ? (FSL_CARD_F_LIST_NEEDS_SORT & mf->F.flags)
           : 1);
    assert(!t->name);
    assert(!t->uuid);
    assert(!t->priorName);
    assert(!t->deckOwnsStrings);
    t->perm = perms;
    if(0==(t->name = fsl_strdup(name))){
      rc = FSL_RC_OOM;
    }else if(uuid && 0==(t->uuid = fsl_strdup(uuid))){
      rc = FSL_RC_OOM;
    }else if(oldName && 0==(t->priorName = fsl_strdup(oldName))){
      rc = FSL_RC_OOM;
    }
    if(rc){
      fsl_card_F_list_pop(&mf->F);
    }
    return rc;
  }
}

int fsl_deck_F_foreach( fsl_deck * d, fsl_card_F_visitor_f cb, void * visitorState ){
  if(!cb) return FSL_RC_MISUSE;
  else{
    fsl_card_F const * fc;
    int rc = fsl_deck_F_rewind(d);
    while( !rc && !(rc=fsl_deck_F_next(d, &fc)) && fc) {
      rc = cb( fc, visitorState );
    }
    return (FSL_RC_BREAK==rc) ? 0 : rc;
  }
}

  
/**
    Output state for fsl_appendf_f_mf() and friends. Used for managing
    the output of a fsl_deck.
 */
struct fsl_deck_out_state {
  /**
      The set of cards being output. We use this to delegate certain
      output bits.
   */
  fsl_deck const * d;
  /**
      Output routine to send manifest to.
   */
  fsl_output_f out;
  /**
      State to pass as the first arg of this->out().
   */
  void * outState;

  /**
     The previously-visited card, for confirming that all cards are in
     proper lexical order.
   */
  fsl_card_F const * prevCard;
  /**
      f() result code, so that we can feed the code back through the
      fsl_appendf() layer. If this is non-0, processing must stop.  We
      "could" use this->error.code instead, but this is simple.
   */
  int rc;
  /**
      Counter for list-visiting routines. Must be re-set before each
      visit loop if the visitor makes use of this (most do not).
   */
  fsl_int_t counter;

  /**
      Incrementally-calculated MD5 sum of all output sent via
      fsl_appendf_f_mf().
   */
  fsl_md5_cx md5;

  /* Holds error state for propagating back to the client. */
  fsl_error error;

  /**
      Scratch buffer for fossilizing bytes and other temporary work.
      This value comes from fsl_cx_scratchpad().
  */
  fsl_buffer * scratch;
};
typedef struct fsl_deck_out_state fsl_deck_out_state;
static const fsl_deck_out_state fsl_deck_out_state_empty = {
NULL/*d*/,
NULL/*out*/,
NULL/*outState*/,
NULL/*prevCard*/,
0/*rc*/,
0/*counter*/,
fsl_md5_cx_empty_m/*md5*/,
fsl_error_empty_m/*error*/,
NULL/*scratch*/
};

/**
    fsl_appendf_f() impl which forwards its data to arg->out(). arg
    must be a (fsl_deck_out_state *). Updates arg->rc to the result of
    calling arg->out(fp->fState, data, n). If arg->out() succeeds then
    arg->md5 is updated to reflect the given data. i.e. this is where
    the Z-card gets calculated incrementally during output of a deck.
*/
static fsl_int_t fsl_appendf_f_mf( void * arg,
                                   char const * data,
                                   fsl_int_t n ){
  fsl_deck_out_state * os = (fsl_deck_out_state *)arg;
  if((n>0)
     && !(os->rc = os->out(os->outState, data, (fsl_size_t)n))
     && (os->md5.isInit)){
    fsl_md5_update( &os->md5, data, (fsl_size_t)n );
  }
  return (0==os->rc)
    ? n
    : -1;
}

/**
    Internal helper for fsl_deck_output(). Appends formatted output to
    os->out() via fsl_appendf_f_mf(). Returns os->rc (0 on success).
 */
static int fsl_deck_append( fsl_deck_out_state * os,
                            char const * fmt, ... ){
  fsl_int_t rc;
  va_list args;
  assert(os);
  assert(fmt && *fmt);
  va_start(args,fmt);
  rc = fsl_appendfv( fsl_appendf_f_mf, os, fmt, args);
  va_end(args);
  if(rc<0 && !os->rc) os->rc = FSL_RC_IO;
  return os->rc;
}

/**
    Fossilizes (inp, inp+len] bytes to os->scratch,
    overwriting any existing contents.
    Updates and returns os->rc.
 */
static int fsl_deck_fossilize( fsl_deck_out_state * os,
                               unsigned char const * inp,
                               fsl_int_t len){
  fsl_buffer_reuse(os->scratch);
  return os->rc = len
    ? fsl_bytes_fossilize(inp, len, os->scratch)
    : 0;
}

/** Confirms that the given card letter is valid for od->d->type, and
    updates os->rc and os->error if it's not. Returns true if it's
    valid.
*/
static bool fsl_deck_out_tcheck(fsl_deck_out_state * os, char letter){
  if(!fsl_card_is_legal(os->d->type, letter)){
    os->rc = fsl_error_set(&os->error, FSL_RC_TYPE,
                           "%c-card is not valid for deck type %s.",
                           letter, fsl_satype_cstr(os->d->type));
  }
  return os->rc ? false : true;
}

/* Appends a UUID-valued card to os from os->d->{{card}} if the given
   UUID is not NULL, else this is a no-op. */
static int fsl_deck_out_uuid( fsl_deck_out_state * os, char card, fsl_uuid_str uuid ){
  if(uuid && fsl_deck_out_tcheck(os, card)){
    if(!fsl_is_uuid(uuid)){
      os->rc = fsl_error_set(&os->error, FSL_RC_RANGE,
                             "Malformed UUID in %c card.", card);
    }else{
      fsl_deck_append(os, "%c %s\n", card, uuid);
    }
  }
  return os->rc;
}

/* Appends the B card to os from os->d->B. */
static int fsl_deck_out_B( fsl_deck_out_state * os ){
  return fsl_deck_out_uuid(os, 'B', os->d->B.uuid);
}


/* Appends the A card to os from os->d->A. */
static int fsl_deck_out_A( fsl_deck_out_state * os ){
  if(os->d->A.name && fsl_deck_out_tcheck(os, 'A')){
    if(!os->d->A.name || !*os->d->A.name){
      os->rc = fsl_error_set(&os->error, FSL_RC_SYNTAX,
                             "A-card is missing its name property");

    }else if(!os->d->A.tgt || !*os->d->A.tgt){
      os->rc = fsl_error_set(&os->error, FSL_RC_SYNTAX,
                             "A-card is missing its tgt property: %s",
                             os->d->A.name);

    }else if(os->d->A.src && !fsl_is_uuid(os->d->A.src)){
      os->rc = fsl_error_set(&os->error, FSL_RC_TYPE,
                             "Invalid src UUID in A-card: name=%s, "
                             "invalid uuid=%s",
                             os->d->A.name, os->d->A.src);
    }else{
      fsl_deck_append(os, "A %F %F",
                      os->d->A.name, os->d->A.tgt);
      if(!os->rc){
        if(os->d->A.src){
          fsl_deck_append(os, " %s", os->d->A.src);
        }
        if(!os->rc) fsl_deck_append(os, "\n");
      }
    }
  }
  return os->rc;
}

/**
    Internal helper for outputing cards which are simple strings.
    str is the card to output (NULL values are ignored), letter is
    the card letter being output. If doFossilize is true then
    the output gets fossilize-formatted.
 */
static int fsl_deck_out_letter_str( fsl_deck_out_state * os, char letter,
                                    char const * str, char doFossilize ){
  if(str && fsl_deck_out_tcheck(os, letter)){
    if(doFossilize){
      fsl_deck_fossilize(os, (unsigned char const *)str, -1);
      if(!os->rc){
        fsl_deck_append(os, "%c %b\n", letter, os->scratch);
      }
    }else{
      fsl_deck_append(os, "%c %s\n", letter, str);
    }
  }
  return os->rc;
}

/* Appends the C card to os from os->d->C. */
static int fsl_deck_out_C( fsl_deck_out_state * os ){
  return fsl_deck_out_letter_str( os, 'C', os->d->C, 1 );
}


/* Appends the D card to os from os->d->D. */
static int fsl_deck_out_D( fsl_deck_out_state * os ){
  if((os->d->D > 0.0) && fsl_deck_out_tcheck(os, 'D')){
    char ds[24];
    if(!fsl_julian_to_iso8601(os->d->D, ds, 1)){
      os->rc = fsl_error_set(&os->error, FSL_RC_RANGE,
                             "D-card contains invalid "
                             "Julian Day value.");
    }else{
      fsl_deck_append(os, "D %s\n", ds);
    }
  }
  return os->rc;
}

/* Appends the E card to os from os->d->E. */
static int fsl_deck_out_E( fsl_deck_out_state * os ){
  if(os->d->E.uuid && fsl_deck_out_tcheck(os, 'E')){
    char ds[24];
    char msPrecision = FSL_SATYPE_EVENT!=os->d->type
      /* The timestamps on Events historically have seconds precision,
         not ms.
      */;
    if(!fsl_is_uuid(os->d->E.uuid)){
      os->rc = fsl_error_set(&os->error, FSL_RC_TYPE,
                             "Invalid UUID in E-card: %s",
                             os->d->E.uuid);
    }
    else if(!fsl_julian_to_iso8601(os->d->E.julian, ds, msPrecision)){
      os->rc = fsl_error_set(&os->error, FSL_RC_TYPE,
                             "Invalid Julian Day value in E-card.");
    }
    else{
      fsl_deck_append(os, "E %s %s\n", ds, os->d->E.uuid);
    }
  }
  return os->rc;
}

/* Appends the G card to os from os->d->G. */
static int fsl_deck_out_G( fsl_deck_out_state * os ){
  return fsl_deck_out_uuid(os, 'G', os->d->G);
}

/* Appends the H card to os from os->d->H. */
static int fsl_deck_out_H( fsl_deck_out_state * os ){
  if(os->d->H && os->d->I){
    return os->rc = fsl_error_set(&os->error, FSL_RC_SYNTAX,
                                  "Forum post may not have both H- and I-cards.");
  }
  return fsl_deck_out_letter_str( os, 'H', os->d->H, 1 );
}

/* Appends the I card to os from os->d->I. */
static int fsl_deck_out_I( fsl_deck_out_state * os ){
  if(os->d->I && os->d->H){
    return os->rc = fsl_error_set(&os->error, FSL_RC_SYNTAX,
                                  "Forum post may not have both H- and I-cards.");
  }
  return fsl_deck_out_uuid(os, 'I', os->d->I);
}


static int fsl_deck_out_F_one(fsl_deck_out_state *os,
                              fsl_card_F const * f){
  int rc;
  char hasOldName;
  char const * zPerm;
  assert(f);
  if(os->prevCard){
    int const cmp = fsl_strcmp(os->prevCard->name, f->name);
    if(0==cmp){
      return fsl_error_set(&os->error, FSL_RC_RANGE,
                           "Duplicate F-card name: %s",
                           f->name);
    }else if(cmp>0){
      return fsl_error_set(&os->error, FSL_RC_RANGE,
                           "Out-of-order F-card names: %s before %s",
                           os->prevCard->name, f->name);
    }
  }
  if(!fsl_is_simple_pathname(f->name, true)){
    return fsl_error_set(&os->error, FSL_RC_RANGE,
                         "Filename is invalid as F-card: %s",
                         f->name);
  }
  if(!f->uuid && !os->d->B.uuid){
    return fsl_error_set(&os->error, FSL_RC_MISUSE,
                         "Baseline manifests may not have F-cards "
                         "without UUIDs (file deletion entries). To "
                         "delete files, simply do not inject an F-card "
                         "for them. Delta manifests, however, require "
                         "NULL UUIDs for deletion entries! File: %s",
                         f->name);
  }

  rc = fsl_deck_fossilize(os, (unsigned char const *)f->name, -1);
  if(!rc) rc = fsl_deck_append(os, "F %b", os->scratch);
  if(!rc && f->uuid){
    assert(fsl_is_uuid(f->uuid));
    rc = fsl_deck_append( os, " %s", f->uuid);
    if(rc) return rc;
  }
  if(f->uuid){
    hasOldName = f->priorName && (0!=fsl_strcmp(f->name,f->priorName));
    switch(f->perm){
      case FSL_FILE_PERM_EXE: zPerm = " x"; break;
      case FSL_FILE_PERM_LINK: zPerm = " l"; break;
      default:
        /* When hasOldName, we have to inject an otherwise optional
           'w' to avoid an ambiguity. Or at least that's what the
           fossil F-card-generating code does.
        */
        zPerm = hasOldName ? " w" : ""; break;
    }
    if(*zPerm) rc = fsl_deck_append( os, "%s", zPerm);
    if(!rc && hasOldName){
      assert(*zPerm);
      rc = fsl_deck_fossilize(os, (unsigned char const *)f->priorName, -1);
      if(!rc) rc = fsl_deck_append( os, " %b", os->scratch);
    }
  }
  if(!rc) fsl_appendf_f_mf(os, "\n", 1);
  return os->rc;
}

static int fsl_deck_out_list_obj( fsl_deck_out_state * os,
                                  char letter,
                                  fsl_list const * li,
                                  fsl_list_visitor_f visitor){
  if(li->used && fsl_deck_out_tcheck(os, letter)){
    os->rc = fsl_list_visit( li, 0, visitor, os );
  }
  return os->rc;
}

static int fsl_deck_out_F( fsl_deck_out_state * os ){
  if(os->d->F.used && fsl_deck_out_tcheck(os, 'F')){
    uint32_t i;
    for(i=0; !os->rc && i <os->d->F.used; ++i){
      os->rc = fsl_deck_out_F_one(os, F_at(&os->d->F, i));
    }
  }
  return os->rc;
}


/**
    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.
 */
int fsl_qsort_cmp_J_cards( void const * lhs, void const * rhs ){
  fsl_card_J const * l = *((fsl_card_J const **)lhs);
  fsl_card_J const * r = *((fsl_card_J const **)rhs);
  /* Compare NULL as larger so that NULLs move to the right. That said,
     we aren't expecting any NULLs. */
  assert(l);
  assert(r);
  if(!l) return r ? 1 : 0;
  else if(!r) return -1;
  else{
    /* The '+' sorts before any legal field name bits (letters). */
    if(l->append != r->append) return r->append - l->append
      /* Footnote: that will break if, e.g. l->isAppend==2 and
         r->isAppend=1, or some such. Shame C89 doesn't have a true
         boolean.
      */;
    else return fsl_strcmp(l->field, r->field);
  }
}

/**
    fsl_list_visitor_f() impl for outputing J cards. obj must
    be a (fsl_card_J *).
 */
static int fsl_list_v_mf_output_card_J(void * obj, void * visitorState ){
  fsl_deck_out_state * os = (fsl_deck_out_state *)visitorState;
  fsl_card_J const * c = (fsl_card_J const *)obj;
  fsl_deck_fossilize( os, (unsigned char const *)c->field, -1 );
  if(!os->rc){
    fsl_deck_append(os, "J %s%b", c->append ? "+" : "", os->scratch);
    if(!os->rc){
      if(c->value && *c->value){
        fsl_deck_fossilize( os, (unsigned char const *)c->value, -1 );
        if(!os->rc){
          fsl_deck_append(os, " %b\n", os->scratch);
        }
      }else{
        fsl_deck_append(os, "\n");
      }
    }
  }
  return os->rc;
}

static int fsl_deck_out_J( fsl_deck_out_state * os ){
  return fsl_deck_out_list_obj(os, 'J', &os->d->J,
                               fsl_list_v_mf_output_card_J);
}

/* Appends the K card to os from os->d->K. */
static int fsl_deck_out_K( fsl_deck_out_state * os ){
  if(os->d->K && fsl_deck_out_tcheck(os, 'K')){
    if(!fsl_is_uuid(os->d->K)){
      os->rc = fsl_error_set(&os->error, FSL_RC_RANGE,
                             "Invalid UUID in K card.");
    }
    else{
      fsl_deck_append(os, "K %s\n", os->d->K);
    }
  }
  return os->rc;
}


/* Appends the L card to os from os->d->L. */
static int fsl_deck_out_L( fsl_deck_out_state * os ){
  return fsl_deck_out_letter_str(os, 'L', os->d->L, 1);
}

/* Appends the N card to os from os->d->N. */
static int fsl_deck_out_N( fsl_deck_out_state * os ){
  return fsl_deck_out_letter_str( os, 'N', os->d->N, 1 );
}

/**
    fsl_list_visitor_f() impl for outputing P cards. obj must
    be a (fsl_deck_out_state *) and obj->counter must be
    set to 0 before running the visit iteration.
 */
static int fsl_list_v_mf_output_card_P(void * obj, void * visitorState ){
  fsl_deck_out_state * os = (fsl_deck_out_state *)visitorState;
  char const * uuid = (char const *)obj;
  int const uLen = uuid ? fsl_is_uuid(uuid) : 0;
  if(!uLen){
    os->rc = fsl_error_set(&os->error, FSL_RC_RANGE,
                           "Invalid UUID in P card.");
  }
  else if(!os->counter++) fsl_appendf_f_mf(os, "P ", 2);
  else fsl_appendf_f_mf(os, " ", 1);
  /* Reminder: fsl_appendf_f_mf() updates os->rc. */
  if(!os->rc){
    fsl_appendf_f_mf(os, uuid, (fsl_size_t)uLen);
  }
  return os->rc;
}


static int fsl_deck_out_P( fsl_deck_out_state * os ){
  if(!fsl_deck_out_tcheck(os, 'P')) return os->rc;
  else if(os->d->P.used){
    os->counter = 0;
    os->rc = fsl_list_visit( &os->d->P, 0, fsl_list_v_mf_output_card_P, os );
    assert(os->counter);
    if(!os->rc) fsl_appendf_f_mf(os, "\n", 1);
  }
#if 1
  /* Arguable: empty P-cards are harmless but cosmetically unsightly. */
  else if(FSL_SATYPE_CHECKIN==os->d->type){
    /*
      Evil ugly hack, primarily for round-trip compatibility with
      manifest #1, which has an empty P card.

      fossil(1) ignores empty P-cards in all cases, and must continue
      to do so for backwards compatibility with rid #1 in all repos.

      Pedantic note: there must be no space between the 'P' and the
      newline.
    */
    fsl_deck_append(os, "P\n");
  }
#endif
  return os->rc;
}

/**
    A comparison routine for qsort(3) which compares fsl_card_Q
    instances in a lexical manner. The order is important for card
    ordering in generated manifests.
*/
static int qsort_cmp_Q_cards( void const * lhs, void const * rhs ){
  fsl_card_Q const * l = *((fsl_card_Q const **)lhs);
  fsl_card_Q const * r = *((fsl_card_Q const **)rhs);
  /* Compare NULL as larger so that NULLs move to the right. That said,
     we aren't expecting any NULLs. */
  assert(l);
  assert(r);
  if(!l) return r ? 1 : 0;
  else if(!r) return -1;
  else{
    /* Lexical sorting must account for the +/- characters, and a '+'
       sorts before '-', which is why this next part may seem
       backwards at first.
    */
    assert(l->type);
    assert(r->type);
    if(l->type<0 && r->type>0) return 1;
    else if(l->type>0 && r->type<0) return -1;
    else return fsl_strcmp(l->target, r->target);
  }
}

/**
    fsl_list_visitor_f() impl for outputing Q cards. obj must
    be a (fsl_deck_out_state *).
*/
static int fsl_list_v_mf_output_card_Q(void * obj, void * visitorState ){
  fsl_deck_out_state * os = (fsl_deck_out_state *)visitorState;
  fsl_card_Q const * cp = (fsl_card_Q const *)obj;
  char const prefix = (cp->type==FSL_CHERRYPICK_ADD)
    ? '+' : '-';
  assert(cp->type);
  assert(cp->target);
  if(cp->type != FSL_CHERRYPICK_ADD &&
     cp->type != FSL_CHERRYPICK_BACKOUT){
    os->rc = fsl_error_set(&os->error, FSL_RC_RANGE,
                           "Invalid type value in Q-card.");
  }else if(!fsl_card_is_legal(os->d->type, 'Q')){
    os->rc = fsl_error_set(&os->error, FSL_RC_TYPE,
                           "Q-card is not valid for deck type %s",
                           fsl_satype_cstr(os->d->type));
  }else if(!fsl_is_uuid(cp->target)){
    os->rc = fsl_error_set(&os->error, FSL_RC_RANGE,
                           "Invalid target UUID in Q-card: %s",
                           cp->target);
  }else if(cp->baseline){
    if(!fsl_is_uuid(cp->baseline)){
      os->rc = fsl_error_set(&os->error, FSL_RC_RANGE,
                             "Invalid baseline UUID in Q-card: %s",
                             cp->baseline);
    }else{
      fsl_deck_append(os, "Q %c%s %s\n", prefix, cp->target, cp->baseline);
    }
  }else{
    fsl_deck_append(os, "Q %c%s\n", prefix, cp->target);
  }
  return os->rc;
}

static int fsl_deck_out_Q( fsl_deck_out_state * os ){
  return fsl_deck_out_list_obj(os, 'Q', &os->d->Q,
                               fsl_list_v_mf_output_card_Q);
}

/**
    Appends the R card from os->d->R to os.
 */
static int fsl_deck_out_R( fsl_deck_out_state * os ){
  if(os->d->R && fsl_deck_out_tcheck(os, 'R')){
    if((FSL_STRLEN_MD5!=fsl_strlen(os->d->R))
            || !fsl_validate16(os->d->R, FSL_STRLEN_MD5)){
      os->rc = fsl_error_set(&os->error, FSL_RC_RANGE,
                             "Malformed MD5 in R-card.");
    }
    else{
      fsl_deck_append(os, "R %s\n", os->d->R);
    }
  }
  return os->rc;
}

/**
    fsl_list_visitor_f() impl for outputing T cards. obj must
    be a (fsl_deck_out_state *).
 */
static int fsl_list_v_mf_output_card_T(void * obj, void * visitorState ){
  fsl_deck_out_state * os = (fsl_deck_out_state *)visitorState;
  fsl_card_T * t = (fsl_card_T *)obj;
  char prefix = 0;
  switch(os->d->type){
    case FSL_SATYPE_TECHNOTE:
      if( t->uuid ){
        return os->rc = fsl_error_set(&os->error, FSL_RC_SYNTAX,
                                      "Non-self-referential T-card is not "
                                      "permitted in a technote.");
      }else if(FSL_TAGTYPE_ADD!=t->type){
        return os->rc = fsl_error_set(&os->error, FSL_RC_SYNTAX,
                                      "Non-ADD T-card is not permitted "
                                      "in a technote.");
      }
      break;
    case FSL_SATYPE_CONTROL:
      if( !t->uuid ){
        return os->rc = fsl_error_set(&os->error, FSL_RC_SYNTAX,
                                      "Self-referential T-card is not "
                                      "permitted in a control artifact.");
      }
      break;
    default:
      break;
  }
  /* Determine the prefix character... */
  switch(t->type){
    case FSL_TAGTYPE_CANCEL: prefix = '-'; break;
    case FSL_TAGTYPE_ADD: prefix = '+'; break;
    case FSL_TAGTYPE_PROPAGATING: prefix = '*'; break;
    default:
      return os->rc = fsl_error_set(&os->error, FSL_RC_TYPE,
                                    "Invalid tag type #%d in T-card.",
                                    t->type);
  }
  if(!t->name || !*t->name){
    return os->rc = fsl_error_set(&os->error, FSL_RC_SYNTAX,
                                  "T-card name may not be empty.");
  }else if(fsl_validate16(t->name, fsl_strlen(t->name))){
    return os->rc = fsl_error_set(&os->error, FSL_RC_SYNTAX,
                                  "T-card name may not be hexadecimal.");
  }else if(t->uuid && !fsl_is_uuid(t->uuid)){
      return os->rc = fsl_error_set(&os->error, FSL_RC_SYNTAX,
                                    "Malformed UUID in T-card: %s",
                                    t->uuid);
  }
  /*
     Fossilize and output the prefix, name, and uuid, or a '*' if no
     uuid is set (which is only legal when tagging the current
     artifact, as '*' is a placeholder for the current artifact's
     UUID, which is not yet known).
  */
  fsl_buffer_reuse(os->scratch);
  fsl_deck_fossilize(os, (unsigned const char *)t->name, -1);
  if(os->rc) return os->rc;
  os->rc = fsl_deck_append(os, "T %c%s %s", prefix,
                           (char const*)os->scratch->mem,
                           t->uuid ? t->uuid : "*");
  if(os->rc) return os->rc;
  if(/*(t->type != FSL_TAGTYPE_CANCEL) &&*/t->value && *t->value){
    /* CANCEL tags historically don't store a value but
       the spec doesn't disallow it and they are harmless
       for (aren't used by) fossil(1). */
    fsl_deck_fossilize(os, (unsigned char const *)t->value, -1);
    if(!os->rc) fsl_appendf_f_mf(os, " ", 1);
    if(!os->rc) fsl_appendf_f_mf(os, (char const*)os->scratch->mem,
                                 (fsl_int_t)os->scratch->used);
  }
  if(!os->rc){
    fsl_appendf_f_mf(os, "\n", 1);
  }
  return os->rc;
}


char fsl_tag_prefix_char( fsl_tagtype_e t ){
  switch(t){
    case FSL_TAGTYPE_CANCEL: return '-';
    case FSL_TAGTYPE_ADD: return '+';
    case FSL_TAGTYPE_PROPAGATING: return '*';
    default:
      return 0;
  }
}

/**
    A comparison routine for qsort(3) which compares fsl_card_T
    instances in a lexical manner based on (type, name, uuid, value).
    The order of those is important for card ordering in generated
    manifests. Interestingly, CANCEL tags (with a '-' prefix) sort
    last, meaning it is possible to cancel a tag set in the same
    manifest because crosslinking processes them in the order given
    (which will be lexical order for all legal manifests).

    Reminder: lhs and rhs must be (fsl_card_T**), as we use this to
    qsort() such lists. When using it to compare two tags, make sure
    to pass ptr-to-ptr.
*/
static int fsl_card_T_cmp( void const * lhs, void const * rhs ){
  fsl_card_T const * l = *((fsl_card_T const **)lhs);
  fsl_card_T const * r = *((fsl_card_T const **)rhs);
  /* Compare NULL as larger so that NULLs move to the right. That said,
     we aren't expecting any NULLs. */
  assert(l);
  assert(r);
  if(!l) return r ? 1 : 0;
  else if(!r) return -1;
  else if(l->type != r->type){
    char const lc = fsl_tag_prefix_char(l->type);
    char const rc = fsl_tag_prefix_char(r->type);
    return (lc<rc) ? -1 : 1;
  }else{
    int rc = fsl_strcmp(l->name, r->name);
    if(rc) return rc;
    else {
      rc = fsl_uuidcmp(l->uuid, r->uuid);
      return rc
        ? rc
        : fsl_strcmp(l->value, r->value);
    }
  }
}

/**
   Confirms that any T-cards in d are properly sorted. If not,
   returns non-0. If err is not NULL, it is updated with a
   description of the problem.

   Possibly fixme one day: this code permits that the same tag/target
   combination may be added or removed, or added as a normal and
   propagating tag, in the same deck. Though that's not technically
   disallowed, we "should" disallow it. That requires a more thorough
   scan of the cards, though.
*/
static int fsl_deck_T_verify_order( fsl_deck const * d, fsl_error * err ){
  if(d->T.used<2) return 0;
  else{
    fsl_size_t i = 0, j;
    int rc = 0;
    fsl_card_T const * tag;
    fsl_card_T const * prev = NULL;
    for( i = 0; i < d->T.used; ++i, prev = tag, rc = 0){
      tag = (fsl_card_T const *)d->T.list[i];
      if(prev){
        if( (rc = fsl_card_T_cmp(&prev, &tag)) >= 0 ){
          if(!err) rc = FSL_RC_SYNTAX;
          else{
            rc = rc
              ? fsl_error_set(err, FSL_RC_SYNTAX,
                              "Invalid T-card order: "
                              "[%c%s] must precede [%c%s]",
                              fsl_tag_prefix_char(prev->type),
                              prev->name,
                              fsl_tag_prefix_char(tag->type),
                              tag->name)
              : fsl_error_set(err, FSL_RC_SYNTAX,
                              "Duplicate T-card: %c%s",
                              fsl_tag_prefix_char(prev->type),
                              prev->name)
              ;
          }
          break;
        }
      }
    }
    /**
       And now, for bonus points: disallow the same tag name/artifact
       combination appearing twice in the deck. Though that's not
       explicitly disallowed by the fossil specs, we "should" disallow
       it. That requires a more thorough scan of the cards, though.

       This logic is NOT in fossil, and though we don't have any such
       tags in the fossil repo, we may have to disable this for
       compatibility's sake. OTOH, we only check this when outputing
       manifests, and we never (aside from testing) have to output
       manifests which were generated by fossil. Thus... this only
       triggers (except for some tests) on manifests generated by
       libfossil, so we can justify having it.
    */
    for( i=0; !rc && i < d->T.used; ++i ){
      fsl_card_T const * t1 = (fsl_card_T const *)d->T.list[i];
      for( j = 0; j < d->T.used; ++j ){
        if(i==j) continue;
        fsl_card_T const * t2 = (fsl_card_T const *)d->T.list[j];
        if(0==fsl_strcmp(t1->name, t2->name)
           && ((!t1->uuid && !t2->uuid)
               || 0==fsl_strcmp(t1->uuid, t2->uuid))){
              rc = fsl_error_set(err, FSL_RC_SYNTAX,
                                 "An artifact may not contain the same "
                                 "T-card name and target artifact "
                                 "multiple times: "
                                 "name=%s target=%s",
                                 t1->name, t1->uuid ? t1->uuid : "*");
              break;
        }
      }
    }
    return rc;
  }
}

/* Appends the T cards to os from os->d->T. */
static int fsl_deck_out_T( fsl_deck_out_state * os ){
  os->rc = fsl_deck_T_verify_order( os->d, &os->error);
  return os->rc
    ? os->rc
    : fsl_deck_out_list_obj(os, 'T', &os->d->T,
                            fsl_list_v_mf_output_card_T);
}

/* Appends the U card to os from os->d->U. */
static int fsl_deck_out_U( fsl_deck_out_state * os ){
  return fsl_deck_out_letter_str(os, 'U', os->d->U, 1);
}

/* Appends the W card to os from os->d->W. */
static int fsl_deck_out_W( fsl_deck_out_state * os ){
  if(os->d->W.used && fsl_deck_out_tcheck(os, 'W')){
    fsl_deck_append(os, "W %"FSL_SIZE_T_PFMT"\n%b\n",
                    (fsl_size_t)os->d->W.used,
                    &os->d->W );
  }
  return os->rc;
}


/* Appends the Z card to os from os' accummulated md5 hash. */
static int fsl_deck_out_Z( fsl_deck_out_state * os ){
  unsigned char digest[16];
  char md5[FSL_STRLEN_MD5+1];
  fsl_md5_final(&os->md5, digest);
  fsl_md5_digest_to_base16(digest, md5);
  assert(!md5[32]);
  os->md5.isInit = 0 /* Keep further output from updating the MD5 */;
  return fsl_deck_append(os, "Z %.*s\n", FSL_STRLEN_MD5, md5);
}

static int qsort_cmp_strings( void const * lhs, void const * rhs ){
  char const * l = *((char const **)lhs);
  char const * r = *((char const **)rhs);
  return fsl_strcmp(l,r);
}

static int fsl_list_v_mf_output_card_M(void * obj, void * visitorState ){
  fsl_deck_out_state * os = (fsl_deck_out_state *)visitorState;
  char const * m = (char const *)obj;
  return fsl_deck_append(os, "M %s\n", m);
}

static int fsl_deck_output_cluster( fsl_deck_out_state * os ){
  if(!os->d->M.used){
    os->rc = fsl_error_set(&os->error, FSL_RC_RANGE,
                           "M-card list may not be empty.");
  }else{
    fsl_deck_out_list_obj(os, 'M', &os->d->M,
                          fsl_list_v_mf_output_card_M);
  }
  return os->rc;
}


/* Helper for fsl_deck_output_CATYPE() */
#define DOUT(LETTER) rc = fsl_deck_out_##LETTER(os); \
  if(rc || os->rc) return os->rc ? os->rc : rc

static int fsl_deck_output_control( fsl_deck_out_state * os ){
  int rc;
  /* Reminder: cards must be output in strict lexical order. */
  DOUT(D);
  DOUT(T);
  DOUT(U);
  return os->rc;
}

static int fsl_deck_output_event( fsl_deck_out_state * os ){
  int rc = 0;
  /* Reminder: cards must be output in strict lexical order. */
  DOUT(C);
  DOUT(D);
  DOUT(E);
  DOUT(N);
  DOUT(P);
  DOUT(T);
  DOUT(U);
  DOUT(W);
  return os->rc;
}

static int fsl_deck_output_mf( fsl_deck_out_state * os ){
  int rc = 0;
  /* Reminder: cards must be output in strict lexical order. */
  DOUT(B);
  DOUT(C);
  DOUT(D);
  DOUT(F);
  DOUT(K);
  DOUT(L);
  DOUT(N);
  DOUT(P);
  DOUT(Q);
  DOUT(R);
  DOUT(T);
  DOUT(U);
  DOUT(W);
  return os->rc;
}


static int fsl_deck_output_ticket( fsl_deck_out_state * os ){
  int rc;
  /* Reminder: cards must be output in strict lexical order. */
  DOUT(D);
  DOUT(J);
  DOUT(K);
  DOUT(U);
  return os->rc;
}

static int fsl_deck_output_wiki( fsl_deck_out_state * os ){
  int rc;
  /* Reminder: cards must be output in strict lexical order. */
  DOUT(C);
  DOUT(D);
  DOUT(L);
  DOUT(N);
  DOUT(P);
  DOUT(U);
  DOUT(W);
  return os->rc;
}

static int fsl_deck_output_attachment( fsl_deck_out_state * os ){
  int rc = 0;
  /* Reminder: cards must be output in strict lexical order. */
  DOUT(A);
  DOUT(C);
  DOUT(D);
  DOUT(N);
  DOUT(U);
  return os->rc;
}

static int fsl_deck_output_forumpost( fsl_deck_out_state * os ){
  int rc;
  /* Reminder: cards must be output in strict lexical order. */
  DOUT(D);
  DOUT(G);
  DOUT(H);
  DOUT(I);
  DOUT(N);
  DOUT(P);
  DOUT(U);
  DOUT(W);
  return os->rc;
}

/**
    Only for testing/debugging purposes, as it allows constructs which
    are not semantically legal and are CERTAINLY not legal to stuff in
    the database.
 */
static int fsl_deck_output_any( fsl_deck_out_state * os ){
  int rc = 0;
  /* Reminder: cards must be output in strict lexical order. */
  DOUT(B);
  DOUT(C);
  DOUT(D);
  DOUT(E);
  DOUT(F);
  DOUT(J);
  DOUT(K);
  DOUT(L);
  DOUT(N);
  DOUT(P);
  DOUT(Q);
  DOUT(R);
  DOUT(T);
  DOUT(U);
  DOUT(W);
  return os->rc;
}

#undef DOUT


int fsl_deck_unshuffle( fsl_deck * d, bool calculateRCard ){
  fsl_list * li;
  int rc = 0;
  if(!d || !d->f) return FSL_RC_MISUSE;
  fsl_cx_err_reset(d->f);
#define SORT(CARD,CMP) li = &d->CARD; fsl_list_sort(li, CMP)
  SORT(J,fsl_qsort_cmp_J_cards);
  SORT(M,qsort_cmp_strings);
  SORT(Q,qsort_cmp_Q_cards);
  SORT(T,fsl_card_T_cmp);
#undef SORT
  if(FSL_SATYPE_CHECKIN!=d->type){
    assert(!fsl_card_is_legal(d->type,'R'));
    assert(!fsl_card_is_legal(d->type,'F'));
  }else{
    assert(fsl_card_is_legal(d->type, 'R') && "in-lib unit testing");
    if(calculateRCard){
      rc = fsl_deck_R_calc(d) /* F-card list is sorted there */;
    }else{
      fsl_deck_F_sort(d);
      if(!d->R){
        rc = fsl_deck_R_set(d,
                            (d->F.used || d->B.uuid || d->P.used)
                            ? NULL
                            : FSL_MD5_INITIAL_HASH)
          /* Special case: for manifests with no (B,F,P)-cards we inject
             the initial-state R-card, analog to the initial checkin
             (RID 1). We need one of (B,F,P,R) to unambiguously identify
             a MANIFEST from a CONTROL, but RID 1 has an empty P-card,
             no F-cards, and no B-card, so it _needs_ an R-card in order
             to be unambiguously a Manifest. That said, that ambiguity
             is/would be harmless in practice because CONTROLs go
             through most of the same crosslinking processes as
             MANIFESTs (the ones which are important for this purpose,
             anyway).
          */;
      }
    }
  }
  return rc;
}

int fsl_deck_output( fsl_deck * d, fsl_output_f out, void * outputState ){
  static const bool allowTypeAny = false
    /* Only enable for debugging/testing. Allows outputing decks of
       type FSL_SATYPE_ANY, which bypasses some validation checks and
       may trigger other validation assertions. And may allow you to
       inject garbage into the repo. So be careful.
    */;

  fsl_deck_out_state OS = fsl_deck_out_state_empty;
  fsl_deck_out_state * const os = &OS;
  fsl_cx * const f = d->f;
  int rc = 0;
  if(!f || !out) return FSL_RC_MISUSE;
  else if(FSL_SATYPE_ANY==d->type){
    if(!allowTypeAny){
      return fsl_cx_err_set(d->f, FSL_RC_TYPE,
                            "Artifact type ANY cannot be"
                            "output unless it is enabled in this "
                            "code (it's dangerous).");
    }
    /* fall through ... */
  }
  rc = fsl_deck_unshuffle(d,
                          (FSL_CX_F_CALC_R_CARD & f->flags)
                          ? ((d->F.used && !d->R) ? 1 : 0)
                          : 0);
  /* ^^^^ unshuffling might install an R-card, so we have to
     do that before checking whether all required cards are
     set... */  
  if(rc) return rc;
  else if(!fsl_deck_has_required_cards(d)){
    return FSL_RC_SYNTAX;
  }

  os->d = d;
  os->out = out;
  os->outState = outputState;
  os->scratch = fsl_cx_scratchpad(f);
  switch(d->type){
    case FSL_SATYPE_CLUSTER:
      rc = fsl_deck_output_cluster(os);
      break;
    case FSL_SATYPE_CONTROL:
      rc = fsl_deck_output_control(os);
      break;
    case FSL_SATYPE_EVENT:
      rc = fsl_deck_output_event(os);
      break;
    case FSL_SATYPE_CHECKIN:
      rc = fsl_deck_output_mf(os);
      break;
    case FSL_SATYPE_TICKET:
      rc = fsl_deck_output_ticket(os);
      break;
    case FSL_SATYPE_WIKI:
      rc = fsl_deck_output_wiki(os);
      break;
    case FSL_SATYPE_ANY:
      assert(allowTypeAny);
      rc = fsl_deck_output_any(os);
      break;
    case FSL_SATYPE_ATTACHMENT:
      rc = fsl_deck_output_attachment(os);
      break;
    case FSL_SATYPE_FORUMPOST:
      rc = fsl_deck_output_forumpost(os);
      break;
    default:
      rc = fsl_cx_err_set(f, FSL_RC_TYPE,
                          "Invalid/unhandled deck type (#%d).",
                          d->type);
      goto end;
  }
  if(!rc){
    rc = fsl_deck_out_Z( os );
  }
  end:
  fsl_cx_scratchpad_yield(f, os->scratch);
  if(os->rc && os->error.code){
    fsl_error_move(&os->error, &f->error);
  }
  fsl_error_clear(&os->error);
  return os->rc ? os->rc : rc;
}

/* Timestamps might be adjusted slightly to ensure that checkins appear
   on the timeline in chronological order.  This is the maximum amount
   of the adjustment window, in days.
*/
#define AGE_FUDGE_WINDOW      (2.0/86400.0)       /* 2 seconds */

/* This is increment (in days) by which timestamps are adjusted for
   use on the timeline.
*/
#define AGE_ADJUST_INCREMENT  (25.0/86400000.0)   /* 25 milliseconds */

/**
   Adds a record in the pending_xlink temp table, to be processed
   when crosslinking is completed. Returns 0 on success, non-0 for
   db error.
*/
static int fsl_deck_crosslink_add_pending(fsl_cx * f, char cType, fsl_uuid_cstr uuid){
  int rc = 0;
  assert(f->cache.isCrosslinking);
  rc = fsl_db_exec(f->dbMain,
                   "INSERT OR IGNORE INTO pending_xlink VALUES('%c%q')",
                   cType, uuid);
  return fsl_cx_uplift_db_error2(f, 0, rc);
}


/** @internal
   
    Add a single entry to the mlink table.  Also add the filename to
    the filename table if it is not there already.
   
    Parameters:
   
    pmid: Record for parent manifest. Use 0 to indicate no parent.

    zFromUuid: UUID for the content in parent (the new ==mlink.pid). 0
    or "" to add file.

    mid: The record ID of the manifest
   
    zToUuid:UUID for the mlink.fid. "" to delete
   
    zFilename: Filename
   
    zPrior: Previous filename. NULL if unchanged 
   
    isPublic:True if mid is not a private manifest
   
    isPrimary: true if pmid is the primary parent of mid.

    mperm: permissions
 */
static
int fsl_mlink_add_one( fsl_cx * f,
                       fsl_id_t pmid, fsl_uuid_cstr zFromUuid,
                       fsl_id_t mid, fsl_uuid_cstr zToUuid,
                       char const * zFilename,
                       char const * zPrior,
                       bool isPublic,
                       bool isPrimary,
                       fsl_fileperm_e mperm){
  fsl_id_t fnid, pfnid, pid, fid;
  fsl_db * db = fsl_cx_db_repo(f);
  fsl_stmt * s1 = NULL;
  int rc;
  bool doInsert = false;
  assert(f);
  assert(db);
  assert(db->beginCount>0);
  //MARKER(("%s() pmid=%d mid=%d\n", __func__, (int)pmid, (int)mid));

  rc = fsl_repo_filename_fnid2(f, zFilename, &fnid, 1);
  if(rc) return rc;
  if( zPrior && *zPrior ){
    rc = fsl_repo_filename_fnid2(f, zPrior, &pfnid, 1);
    if(rc) return rc;
  }else{
    pfnid = 0;
  }
  if( zFromUuid && *zFromUuid ){
    pid = fsl_uuid_to_rid2(f, zFromUuid, FSL_PHANTOM_PUBLIC);
    if(pid<0){
      assert(f->error.code);
      return f->error.code;
    }
    assert(pid>0);
  }else{
    pid = 0;
  }

  if( zToUuid && *zToUuid ){
    fid = fsl_uuid_to_rid2(f, zToUuid, FSL_PHANTOM_PUBLIC);
    if(fid<0){
      assert(f->error.code);
      return f->error.code;
    }else if( isPublic ){
      rc = fsl_content_make_public(f, fid);
      if(rc) return rc;
    }
  }else{
    fid = 0;
  }

  if(isPrimary){
    doInsert = true;
  }else{
    fsl_stmt * sInsCheck = 0;
    rc = fsl_db_prepare_cached(db, &sInsCheck,
                               "SELECT 1 FROM mlink WHERE "
                               "mid=? AND fnid=? AND NOT isaux"
                               "/*%s()*/",__func__);
    if(rc){
      rc = fsl_cx_uplift_db_error(f, db);
      goto end;
    }
    fsl_stmt_bind_id(sInsCheck, 1, mid);
    fsl_stmt_bind_id(sInsCheck, 2, fnid);
    rc = fsl_stmt_step(sInsCheck);
    fsl_stmt_cached_yield(sInsCheck);
    doInsert = (FSL_RC_STEP_ROW==rc) ? true : false;
    rc = 0;
  }
  if(doInsert){
    rc = fsl_db_prepare_cached(db, &s1,
                               "INSERT INTO mlink("
                               "mid,fid,pmid,pid,"
                               "fnid,pfnid,mperm,isaux"
                               ")VALUES("
                               ":m,:f,:pm,:p,:n,:pfn,:mp,:isaux"
                               ")"
                               "/*%s()*/",__func__);
    if(!rc){
      fsl_stmt_bind_id_name(s1, ":m", mid);
      fsl_stmt_bind_id_name(s1, ":f", fid);
      fsl_stmt_bind_id_name(s1, ":pm", pmid);
      fsl_stmt_bind_id_name(s1, ":p", pid);
      fsl_stmt_bind_id_name(s1, ":n", fnid);
      fsl_stmt_bind_id_name(s1, ":pfn", pfnid);
      fsl_stmt_bind_id_name(s1, ":mp", mperm);    
      fsl_stmt_bind_int32_name(s1, ":isaux", isPrimary ? 0 : 1);    
      rc = fsl_stmt_step(s1);
      fsl_stmt_cached_yield(s1);
      if(FSL_RC_STEP_DONE==rc){
        rc = 0;
      }else{
        fsl_cx_uplift_db_error(f, db);
      }
    }
  }
  if(!rc && pid>0 && fid){
    /* Reminder to self: this costs almost 1ms per checkin in very
       basic tests with 2003 checkins on my NUC unit. */
    rc = fsl_content_deltify(f, pid, fid, 0);
  }
  end:
  return rc;  
}

/**
    Do a binary search to find a file in d->F.list.  
   
    As an optimization, guess that the file we seek is at index
    d->F.cursor.  That will usually be the case.  If it is not found
    there, then do the actual binary search.

    Update d->F.cursor to be the index of the file that is found.

    If d->f is NULL then this perform a case-sensitive search,
    otherwise it uses case-sensitive or case-insensitive,
    depending on f->cache.caseInsensitive.    

    If the 3rd argument is not NULL and non-NULL is returned then
    *atNdx gets set to the d->F.list index of the resulting object.
    If NULL is returned, *atNdx is not modified.

    Reminder to self: if this requires a non-const deck (and it does
    right now) then the whole downstream chain will require a
    non-const instance or they'll have to make local copies to make
    the manipulation of d->F.cursor legal (but that would break
    following of baselines without yet more trickery).

    Reminder to self:

    Fossil(1) added another parameter to this since it was ported,
    indicating whether only an exact match or the "closest match" is
    acceptable, but currently (2021-03-10) only the fusefs module uses
    the closest-match option. It's a trivial code change but currently
    looks like YAGNI.
*/
static fsl_card_F * fsl_deck_F_seek_base(fsl_deck * d,
                                         char const * zName,
                                         uint32_t * atNdx ){
  /* Maintenance reminder: this algo relies on the various
     counters being signed. */
  fsl_int_t lwr, upr;
  int c;
  fsl_int_t i;
  assert(d);
  assert(zName && *zName);
  if(!d->F.used) return NULL;
  else if(FSL_CARD_F_LIST_NEEDS_SORT & d->F.flags){
    fsl_card_F_list_sort(&d->F);
  }
#define FCARD(NDX) F_at(&d->F, (NDX))
  lwr = 0;
  upr = d->F.used-1;
  if( d->F.cursor>=lwr && d->F.cursor<upr ){
    c = (d->f && d->f->cache.caseInsensitive)
      ? fsl_stricmp(FCARD(d->F.cursor+1)->name, zName)
      : fsl_strcmp(FCARD(d->F.cursor+1)->name, zName);
    if( c==0 ){
      if(atNdx) *atNdx = (uint32_t)d->F.cursor+1;
      return FCARD(++d->F.cursor);
    }else if( c>0 ){
      upr = d->F.cursor;
    }else{
      lwr = d->F.cursor+1;
    }
  }
  while( lwr<=upr ){
    i = (lwr+upr)/2;
    c = (d->f && d->f->cache.caseInsensitive)
      ? fsl_stricmp(FCARD(i)->name, zName)
      : fsl_strcmp(FCARD(i)->name, zName);
    if( c<0 ){
      lwr = i+1;
    }else if( c>0 ){
      upr = i-1;
    }else{
      d->F.cursor = i;
      if(atNdx) *atNdx = (uint32_t)i;
      return FCARD(i);
    }
  }
  return NULL;
#undef FCARD
}

fsl_card_F * fsl_deck_F_seek(fsl_deck * const d, const char *zName){
  fsl_card_F *pFile;
  assert(d);
  assert(zName && *zName);
  if(!d || (FSL_SATYPE_CHECKIN!=d->type) || !zName || !*zName
     || !d->F.used) return NULL;
  pFile = fsl_deck_F_seek_base(d, zName, NULL);
  if( !pFile &&
      (d->B.baseline /* we have a baseline or... */
       || (d->f && d->B.uuid) /* we can load the baseline */
       )){
        /* Check baseline manifest...

           Sidebar: while the delta manifest model outwardly appears
           to support recursive delta manifests, fossil(1) does not
           use them and there would seem to be little practical use
           for them (no notable size benefit for the majority of
           cases), so we're not recursing here.
         */
    int const rc = d->B.baseline ? 0 : fsl_deck_baseline_fetch(d);
    if(rc){
      assert(d->f->error.code);
    }else if( d->B.baseline ){
      assert(d->B.baseline->f && "How can this happen?");
      assert((d->B.baseline->f == d->f) &&
             "Universal laws are out of balance.");
      pFile = fsl_deck_F_seek_base(d->B.baseline, zName, NULL);
      if(pFile){
        assert(pFile->uuid &&
               "Per fossil-dev thread with DRH on 20140422, "
               "baselines never have removed files.");
      }
    }
  }
  return pFile;
}

fsl_card_F const * fsl_deck_F_search(fsl_deck *d, const char *zName){
  assert(d);
  return fsl_deck_F_seek(d, zName);
}

int fsl_deck_F_set( fsl_deck * d, char const * zName,
                    char const * uuid,
                    fsl_fileperm_e perms, 
                    char const * priorName){
  uint32_t fcNdx = 0;
  fsl_card_F * fc = 0;
  if(d->uuid || d->rid>0){
    return fsl_cx_err_set(d->f, FSL_RC_MISUSE,
                          "%s() cannot be applied to a saved deck.",
                          __func__);
  }else if(!fsl_deck_check_type(d, 'F')){
    return d->f->error.code;
  }
  fc = fsl_deck_F_seek_base(d, zName, &fcNdx);
  if(!uuid){
    if(fc){
      fsl_card_F_list_remove(&d->F, fcNdx);
      return 0;
    }else{
      return FSL_RC_NOT_FOUND;
    }
  }else if(!fsl_is_uuid(uuid)){
    return fsl_cx_err_set(d->f, FSL_RC_RANGE,
                          "Invalid UUID for F-card.");
  }
  if(fc){
    /* Got a match. Replace its contents. */
    char * n = 0;
    if(!fc->deckOwnsStrings){
      /* We can keep fc->name but need a tiny bit of hoop-jumping
         to do so. */
      n = fc->name;
      fc->name = 0;
    }
    fsl_card_F_clean(fc);
    assert(!fc->deckOwnsStrings);
    if(!(fc->name = n ? n : fsl_strdup(zName))) return FSL_RC_OOM;
    if(!(fc->uuid = fsl_strdup(uuid))) return FSL_RC_OOM;
    if(priorName && *priorName){
      if(!fsl_is_simple_pathname(priorName, 1)){
        return fsl_cx_err_set(d->f, FSL_RC_RANGE,
                              "Invalid priorName for F-card "
                              "(simple form required): %s", priorName);
      }else if(!(fc->priorName = fsl_strdup(priorName))){
        return FSL_RC_OOM;
      }
    }
    fc->perm = perms;
    return 0;
  }else{
    return fsl_deck_F_add(d, zName, uuid, perms, priorName);
  }
}

int fsl_deck_F_set_content( fsl_deck * d, char const * zName,
                            fsl_buffer const * src,
                            fsl_fileperm_e perm, 
                            char const * priorName){
  fsl_uuid_str zHash = 0;
  fsl_id_t rid = 0;
  fsl_id_t prevRid = 0;
  int rc = 0;
  if(d->uuid || d->rid>0){
    return fsl_cx_err_set(d->f, FSL_RC_MISUSE,
                          "%s() cannot be applied to a saved deck.",
                          __func__);
  }else if(!fsl_cx_transaction_level(d->f)){
    return fsl_cx_err_set(d->f, FSL_RC_MISUSE,
                          "%s() requires that a transaction is active.",
                          __func__);
  }
  rc = fsl_repo_blob_lookup(d->f, src, &rid, &zHash);
  if(rc && FSL_RC_NOT_FOUND!=rc) goto end;
  assert(zHash);
  if(!rid){
    fsl_card_F const * fc;
    /* This is new content. Save it, then see if we have a previous version
       to delta against this one. */
    rc = fsl_content_put_ex(d->f, src, zHash, 0, 0, false, &rid);
    if(rc) goto end;
    fc = fsl_deck_F_seek(d, zName);
    if(fc){
      prevRid = fsl_uuid_to_rid(d->f, fc->uuid);
      if(prevRid<0) goto end;
      else if(!prevRid){
        assert(!"cannot happen");
        rc = fsl_cx_err_set(d->f, FSL_RC_NOT_FOUND,
                            "Cannot find RID of file content %s [%s]\n",
                            fc->name, fc->uuid);
        goto end;
      }
      rc = fsl_content_deltify(d->f, prevRid, rid, false);
      if(rc) goto end;
    }
  }
  rc = fsl_deck_F_set(d, zName, zHash, perm, priorName);
  end:
  fsl_free(zHash);
  return rc;
}

void fsl_deck_clean_cards(fsl_deck * d, char const * letters){
  char const * c = letters
    ? letters
    : "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  for( ; *c; ++c ){
    switch(*c){
      case 'A': fsl_deck_clean_A(d); break;
      case 'B': fsl_deck_clean_B(d); break;
      case 'C': fsl_deck_clean_C(d); break;
      case 'D': d->D = 0.0; break;
      case 'E': fsl_deck_clean_E(d); break;
      case 'F': fsl_deck_clean_F(d); break;
      case 'G': fsl_deck_clean_G(d); break;
      case 'H': fsl_deck_clean_H(d); break;
      case 'I': fsl_deck_clean_I(d); break;
      case 'J': fsl_deck_clean_J(d,true); break;
      case 'K': fsl_deck_clean_K(d); break;
      case 'L': fsl_deck_clean_L(d); break;
      case 'M': fsl_deck_clean_M(d); break;
      case 'N': fsl_deck_clean_N(d); break;
      case 'P': fsl_deck_clean_P(d); break;
      case 'Q': fsl_deck_clean_Q(d); break;
      case 'R': fsl_deck_clean_R(d); break;
      case 'T': fsl_deck_clean_T(d); break;
      case 'U': fsl_deck_clean_U(d); break;
      case 'W': fsl_deck_clean_W(d); break;
      default: break;
    }
  }
}

int fsl_deck_derive(fsl_deck * d){
  int rc = 0;
  if(!d->uuid || d->rid<=0) return FSL_RC_MISUSE;
  else if(FSL_SATYPE_CHECKIN!=d->type) return FSL_RC_TYPE;
  fsl_deck_clean_P(d);
  rc = fsl_list_append(&d->P, d->uuid);
  if(rc) return rc;
  d->uuid = NULL;
  d->rid = 0;
  fsl_deck_clean_cards(d, "ACDEGHIJKLMNQRTUW");
  while(d->B.uuid){
    /* This is a delta manifest. Convert this deck into a baseline by
       build a new, complete F-card list. */
    fsl_card_F const * fc;
    fsl_card_F_list flist = fsl_card_F_list_empty;
    uint32_t fCount = 0;
    rc = fsl_deck_F_rewind(d);
    if(rc) return rc;
    while( 0==(rc=fsl_deck_F_next(d, &fc)) && fc ){
      ++fCount;
    }
    rc = fsl_deck_F_rewind(d);
    assert(0==rc
           && "fsl_deck_F_rewind() cannot fail after initial call.");
    assert(0==d->F.cursor);
    assert(0==d->B.baseline->F.cursor);
    rc = fsl_card_F_list_reserve(&flist, fCount);
    if(rc) break;
    while( 1 ){
      rc = fsl_deck_F_next(d, &fc);
      if(rc || !fc) break;
      fsl_card_F * const fNew = fsl_card_F_list_push(&flist);
      assert(fc->uuid);
      assert(fc->name);
      /* We must copy these strings because their ownership is
         otherwise unmanageable. e.g. they might live in d->content
         or d->B.baseline->content. */
      if(!(fNew->name = fsl_strdup(fc->name))
         || !(fNew->uuid = fsl_strdup(fc->uuid))){
        /* Reminder: we do not want/need to copy fc->priorName. Those
           renames were already applied in the parent checkin. */
        rc = FSL_RC_OOM;
        break;
      }
      fNew->perm = fc->perm;
    }
    fsl_deck_clean_B(d);
    fsl_deck_clean_F(d);
    if(rc) fsl_card_F_list_finalize(&flist);
    else d->F = flist/*transfer ownership*/;
    break;
  }
  return rc;
}

/**
    Returns true if repo contains an mlink entry where mid=rid, else
    false.
*/
static bool fsl_repo_has_mlink_mid( fsl_db * repo, fsl_id_t rid ){
#if 0
  return fsl_db_exists(repo,
                       "SELECT 1 FROM mlink WHERE mid=%"FSL_ID_T_PFMT,
                       rid);
#else
  fsl_stmt * st = NULL;
  bool gotone = false;
  int rc = fsl_db_prepare_cached(repo, &st,
                                 "SELECT 1 FROM mlink WHERE mid=?"
                                 "/*%s()*/",__func__);
  
  if(!rc){
    fsl_stmt_bind_id(st, 1, rid);
    rc = fsl_stmt_step(st);
    fsl_stmt_cached_yield(st);
    gotone = rc==FSL_RC_STEP_ROW;
  }
  return gotone;
#endif
}

static bool fsl_repo_has_mlink_pmid_mid( fsl_db * repo, fsl_id_t pmid, fsl_id_t mid ){
  fsl_stmt * st = NULL;
  int rc = fsl_db_prepare_cached(repo, &st,
                                 "SELECT 1 FROM mlink WHERE mid=? "
                                 "AND pmid=?"
                                 "/*%s()*/",__func__);
  if(!rc){
    fsl_stmt_bind_id(st, 1, mid);
    fsl_stmt_bind_id(st, 2, pmid);
    rc = fsl_stmt_step(st);
    fsl_stmt_cached_yield(st);
    if( rc==FSL_RC_STEP_ROW ) rc = 0;
  }
  /* MARKER(("fsl_repo_has_mlink_mid(%d) rc=%d\n", (int)rid, rc)); */
  return rc ? false : true;
}

/**
    Add mlink table entries associated with manifest cid, pChild.  The
    parent manifest is pid, pParent.  One of either pChild or pParent
    will be NULL and it will be computed based on cid/pid.
   
    A single mlink entry is added for every file that changed content,
    name, and/or permissions going from pid to cid.
   
    Deleted files have mlink.fid=0.
   
    Added files have mlink.pid=0.
   
    File added by merge have mlink.pid=-1.

    Edited files have both mlink.pid!=0 and mlink.fid!=0

    Comments from the original implementation:

    Many mlink entries for merge parents will only be added if another
    mlink entry already exists for the same file from the primary
    parent.  Therefore, to ensure that all merge-parent mlink entries
    are properly created:

    (1) Make this routine a no-op if pParent is a merge parent and the
        primary parent is a phantom.

    (2) Invoke this routine recursively for merge-parents if pParent
        is the primary parent.
*/
static int fsl_mlink_add( fsl_cx * f,
                          fsl_id_t pmid, fsl_deck /*const*/ * pParent,
                          fsl_id_t cid, fsl_deck /*const*/ * pChild,
                          bool isPrimary){
  fsl_buffer otherContent = fsl_buffer_empty;
  fsl_id_t otherRid;
  fsl_size_t i = 0;
  int rc = 0;
  fsl_card_F const * pChildFile = NULL;
  fsl_card_F const * pParentFile = NULL;
  fsl_deck dOther = fsl_deck_empty;
  fsl_db * const db = fsl_cx_db_repo(f);
  bool isPublic;
  assert(db);
  assert(db->beginCount>0);
  /* If mlink table entires are already set for pmid/cid, then abort
     early doing no work.
  */
  //MARKER(("%s() pmid=%d cid=%d\n", __func__, (int)pmid, (int)cid));
  if(fsl_repo_has_mlink_pmid_mid(db, pmid, cid)) return 0;
  /* Compute the value of the missing pParent or pChild parameter.
     Fetch the baseline checkins for both.
  */
  assert( pParent==0 || pChild==0 );
  if( pParent ){
    assert(!pChild);
    pChild = &dOther;
    otherRid = cid;
  }else{
    pParent = &dOther;
    otherRid = pmid;
  }

  if(otherRid && !fsl_cx_mcache_search(f, otherRid, &dOther)){
    rc = otherRid ? fsl_content_get(f, otherRid, &otherContent) : 0;
    if(rc){
      /* fossil(1) simply ignores errors here and returns. We'll ignore
         the phantom case because (1) erroring out here would be bad and
         (2) fossil does so. The exact implications of doing so are
         unclear, though. */
      if(FSL_RC_PHANTOM==rc){
        rc = 0;
      }else if(!f->error.msg.used && FSL_RC_OOM!=rc){
        rc = fsl_cx_err_set(f, rc,
                            "Fetching content of rid %"FSL_ID_T_PFMT" failed: %s",
                            otherRid, fsl_rc_cstr(rc));
      }
      goto end;
    }
    if( !otherContent.used ){
      /* ??? fossil(1) ignores this case and returns. */
      fsl_buffer_clear(&otherContent)/*for empty file case*/;
      rc = 0;
      goto end;
    }
    dOther.f = f;
    rc = fsl_deck_parse2(&dOther, &otherContent, otherRid);
    assert(dOther.f);
    if(rc) goto end;
  }
  if( (pParent->f && (rc=fsl_deck_baseline_fetch(pParent)))
      || (pChild->f && (rc=fsl_deck_baseline_fetch(pChild)))){
    goto end;
  }
  isPublic = !fsl_content_is_private(f, cid);

  /* If pParent is not the primary parent of pChild, and the primary
  ** parent of pChild is a phantom, then abort this routine without
  ** doing any work.  The mlink entries will be computed when the
  ** primary parent dephantomizes.
  */
  if( !isPrimary && otherRid==cid ){
    assert(pChild->P.used);
    if(!fsl_db_exists(db,"SELECT 1 FROM blob WHERE uuid=%Q AND size>0",
                      (char const *)pChild->P.list[0])){
      rc = 0;
      fsl_cx_mcache_insert(f, &dOther);
      goto end;
    }
  }

  if(pmid>0){
    /* Try to make the parent manifest a delta from the child, if that
       is an appropriate thing to do.  For a new baseline, make the 
       previous baseline a delta from the current baseline.
    */
    if( (pParent->B.uuid==0)==(pChild->B.uuid==0) ){
      rc = fsl_content_deltify(f, pmid, cid, 0);
    }else if( pChild->B.uuid==NULL && pParent->B.uuid!=NULL ){
      rc = fsl_content_deltify(f, pParent->B.baseline->rid, cid, 0);
    }
    if(rc) goto end;
  }

  /* Remember all children less than a few seconds younger than their parent,
     as we might want to fudge the times for those children.
  */
  if( f->cache.isCrosslinking &&
      (pChild->D < pParent->D+AGE_FUDGE_WINDOW)
  ){
    rc = fsl_db_exec(db, "INSERT OR REPLACE INTO time_fudge VALUES"
                     "(%"FSL_ID_T_PFMT", %"FSL_JULIAN_T_PFMT
                     ", %"FSL_ID_T_PFMT", %"FSL_JULIAN_T_PFMT");",
                     pParent->rid, pParent->D,
                     pChild->rid, pChild->D);
    if(rc) goto end;
  }

  /* First look at all files in pChild, ignoring its baseline.  This
     is where most of the changes will be found.
  */  
#define FCARD(DECK,NDX) \
  ((((NDX)<(DECK)->F.used)) \
   ? F_at(&(DECK)->F,NDX)  \
   : NULL)
  for(i=0, pChildFile=FCARD(pChild,0);
      i<pChild->F.used;
      ++i, pChildFile=FCARD(pChild,i)){
    fsl_fileperm_e const mperm = pChildFile->perm;
    if( pChildFile->priorName ){
      pParentFile = pmid
        ? fsl_deck_F_seek(pParent, pChildFile->priorName)
        : 0;
      if( pParentFile ){
        /* File with name change */
        /*
          libfossil checkin 8625a31eff708dea93b16582e4ec5d583794d1af
          contains these two interesting F-cards:

F src/net/wanderinghorse/libfossil/FossilCheckout.java
F src/org/fossil_scm/libfossil/Checkout.java 6e58a47089d3f4911c9386c25bac36c8e98d4d21 w src/net/wanderinghorse/libfossil/FossilCheckout.java

          Note the placement of FossilCheckout.java (twice).

          Up until then, i thought a delete/rename combination was not possible.
        */
        rc = fsl_mlink_add_one(f, pmid, pParentFile->uuid,
                               cid, pChildFile->uuid, pChildFile->name,
                               pChildFile->priorName, isPublic,
                               isPrimary, mperm);
      }else{
         /* File name changed, but the old name is not found in the parent!
            Treat this like a new file. */
        rc = fsl_mlink_add_one(f, pmid, 0, cid, pChildFile->uuid,
                               pChildFile->name, 0,
                               isPublic, isPrimary, mperm);
      }
    }else if(pmid){
      pParentFile = fsl_deck_F_seek(pParent, pChildFile->name);
      if(!pParentFile || !pParentFile->uuid){
        /* Parent does not have it or it was removed in parent. */
        if( pChildFile->uuid ){
          /* A new or re-added file */
          rc = fsl_mlink_add_one(f, pmid, 0, cid, pChildFile->uuid,
                                 pChildFile->name, 0,
                                 isPublic, isPrimary, mperm);
        }
      }
      else if( fsl_strcmp(pChildFile->uuid, pParentFile->uuid)!=0
                || (pParentFile->perm!=mperm) ){
         /* Changes in file content or permissions */
        rc = fsl_mlink_add_one(f, pmid, pParentFile->uuid,
                               cid, pChildFile->uuid,
                               pChildFile->name, 0,
                               isPublic, isPrimary, mperm);
      }
    }
  } /* end pChild card list loop */
  if(rc) goto end;
  else if( pParent->B.uuid && pChild->B.uuid ){
    /* Both parent and child are delta manifests.  Look for files that
       are deleted or modified in the parent but which reappear or revert
       to baseline in the child and show such files as being added or changed
       in the child. */
    for(i=0, pParentFile=FCARD(pParent,0);
        i<pParent->F.used;
        ++i, pParentFile = FCARD(pParent,i)){
      if( pParentFile->uuid ){
        pChildFile = fsl_deck_F_seek_base(pChild, pParentFile->name, NULL);
        if( !pChildFile || !pChildFile->uuid){
          /* The child file reverts to baseline or is deleted.
             Show this as a change. */
          if(!pChildFile){
            pChildFile = fsl_deck_F_seek(pChild, pParentFile->name);
          }
          if( pChildFile && pChildFile->uuid ){
            rc = fsl_mlink_add_one(f, pmid, pParentFile->uuid, cid,
                                   pChildFile->uuid, pChildFile->name,
                                   0, isPublic, isPrimary,
                                   pChildFile->perm);
          }
        }
      }else{
        /* Was deleted in the parent. */
        pChildFile = fsl_deck_F_seek(pChild, pParentFile->name);
        if( pChildFile && pChildFile->uuid ){
          /* File resurrected in the child after having been deleted in
             the parent.  Show this as an added file. */
          rc = fsl_mlink_add_one(f, pmid, 0, cid, pChildFile->uuid,
                                 pChildFile->name, 0, isPublic,
                                 isPrimary, pChildFile->perm);
        }
      }
      if(rc) goto end;
    }
    assert(0==rc);
  }else if( pmid && !pChild->B.uuid ){
    /* pChild is a baseline with a parent.  Look for files that are
       present in pParent but are missing from pChild and mark them as
       having been deleted. */
    fsl_card_F const * cfc = NULL;
    fsl_deck_F_rewind(pParent);
    while( (0==(rc=fsl_deck_F_next(pParent,&cfc))) && cfc){
      pParentFile = cfc;
      pChildFile = fsl_deck_F_seek(pChild, pParentFile->name);
      if( (!pChildFile || !pChildFile->uuid) && pParentFile->uuid ){
        rc = fsl_mlink_add_one(f, pmid, pParentFile->uuid, cid, 0,
                               pParentFile->name, 0, isPublic,
                               isPrimary, pParentFile->perm);
      }
    }
    if(rc) goto end;
  }

  fsl_cx_mcache_insert(f, &dOther);
  
  /* If pParent is the primary parent of pChild, also run this analysis
  ** for all merge parents of pChild */
  if( pmid && isPrimary ){
    for(i=1; i<pChild->P.used; i++){
      pmid = fsl_uuid_to_rid(f, (char const*)pChild->P.list[i]);
      if( pmid<=0 ) continue;
      rc = fsl_mlink_add(f, pmid, 0, cid, pChild, false);
      if(rc) goto end;
    }
    for(i=0; i<pChild->Q.used; i++){
      fsl_card_Q const * q = (fsl_card_Q const *)pChild->Q.list[i];
      if( q->type>0 && (pmid = fsl_uuid_to_rid(f, q->target))>0 ){
        rc = fsl_mlink_add(f, pmid, 0, cid, pChild, false);
        if(rc) goto end;
      }
    }
  }

  end:
  fsl_deck_finalize(&dOther);
  fsl_buffer_clear(&otherContent);
  if(rc && !f->error.code && db->error.code){
    rc = fsl_cx_uplift_db_error(f, db);
  }
  return rc;
#undef FCARD
}

/**
   Apply all tags defined in deck d. If parentId is >0 then any
   propagating tags from that parent are well and duly propagated.
   Returns 0 on success. Potential TODO: if parentId<=0 and
   d->P.used>0 then use d->P.list[0] in place of parentId.
*/
static int fsl_deck_crosslink_apply_tags(fsl_cx * f, fsl_deck *d,
                                         fsl_db * db, fsl_id_t rid,
                                         fsl_id_t parentId){
  int rc = 0;
  fsl_size_t i;
  fsl_list const * li = &d->T;
  double tagTime = d->D;
  if(li->used && tagTime<=0){
    tagTime = fsl_db_julian_now(db);
    if(tagTime<=0){
      rc = FSL_RC_DB;
      goto end;
    }
  }      
  for( i = 0; !rc && (i < li->used); ++i){
    fsl_id_t tid;
    fsl_card_T const * tag = (fsl_card_T const *)li->list[i];
    assert(tag);
    if(!tag->uuid){
      tid = rid;
    }else{
      tid = fsl_uuid_to_rid( f, tag->uuid);
    }
    if(tid<0){
      assert(f->error.code);
      rc = f->error.code;
      break;
    }else if(0==tid){
      rc = fsl_cx_err_set(f, FSL_RC_RANGE,
                          "Could not get RID for [%.12s].",
                          tag->uuid);
      break;
    }
    rc = fsl_tag_insert(f, tag->type,
                        tag->name, tag->value,
                        rid, tagTime, tid, NULL);
  }
  if( !rc && (parentId>0) ){
    rc = fsl_tag_propagate_all(f, parentId);
  }
  end:
  return rc;
}

/**
   Part of the checkin crosslink machinery: create all appropriate
   plink and mlink table entries for d->P.

   If parentId is not NULL, *parentId gets assigned to the rid of the
   first parent, or 0 if d->P is empty.
*/
static int fsl_deck_add_checkin_linkages(fsl_deck *d, fsl_id_t * parentId){
  int rc = 0;
  fsl_size_t nLink = 0;
  char zBaseId[30] = {0}/*RID of baseline or "NULL" if no baseline */;
  fsl_size_t i;
  fsl_stmt q = fsl_stmt_empty;
  fsl_id_t _parentId = 0;
  fsl_cx * const f = d->f;
  fsl_db * const db = fsl_cx_db_repo(f);
  assert(f && db);
  if(!parentId) parentId = &_parentId;
  if(d->B.uuid){
    fsl_id_t const baseid = d->B.baseline
      ? d->B.baseline->rid
      : fsl_uuid_to_rid(d->f, d->B.uuid);
    if(baseid<0){
      rc = d->f->error.code;
      assert(0 != rc);
      goto end;
    }
    assert(baseid>0);
    fsl_snprintf( zBaseId, sizeof(zBaseId),
                  "%"FSL_ID_T_PFMT,
                  baseid );
        
  }else{
    fsl_snprintf( zBaseId, sizeof(zBaseId), "NULL" );
  }
  *parentId = 0;
  for(i=0; i<d->P.used; ++i){
    char const * parentUuid = (char const *)d->P.list[i];
    fsl_id_t const pid = fsl_uuid_to_rid2(f, parentUuid, FSL_PHANTOM_PUBLIC);
    if(pid<0){
      assert(f->error.code);
      rc = f->error.code;
      goto end;
    }
    rc = fsl_db_exec(db, "INSERT OR IGNORE "
                     "INTO plink(pid, cid, isprim, mtime, baseid) "
                     "VALUES(%"FSL_ID_T_PFMT", %"FSL_ID_T_PFMT
                     ", %d, %"FSL_JULIAN_T_PFMT", %s)",
                     pid, d->rid,
                     ((i==0) ? 1 : 0), d->D, zBaseId);
    if(rc) goto end;
    if(0==i) *parentId = pid;
  }
  rc = fsl_mlink_add(f, *parentId, NULL, d->rid, d, true);
  if(rc) goto end;
  nLink = d->P.used;
  for(i=0; i<d->Q.used; ++i){
    fsl_card_Q const * q = (fsl_card_Q const *)d->Q.list[i];
    if(q->type>0) ++nLink;
  }
  if(nLink>1){
    /* https://www.fossil-scm.org/index.html/info/8e44cf6f4df4f9f0 */
    /* Change MLINK.PID from 0 to -1 for files that are added by merge. */
    rc = fsl_db_exec(db,
                     "UPDATE mlink SET pid=-1"
                     " WHERE mid=%"FSL_ID_T_PFMT
                     "   AND pid=0"
                     "   AND fnid IN "
                     "  (SELECT fnid FROM mlink WHERE mid=%"FSL_ID_T_PFMT
                     " GROUP BY fnid"
                     "    HAVING count(*)<%d)",
                     d->rid, d->rid, (int)nLink
                     );
    if(rc) goto end;
  }
  rc = fsl_db_prepare(db, &q,
                      "SELECT cid, isprim FROM plink "
                      "WHERE pid=%"FSL_ID_T_PFMT,
                      d->rid);
  while( !rc && (FSL_RC_STEP_ROW==(rc=fsl_stmt_step(&q))) ){
    fsl_id_t const cid = fsl_stmt_g_id(&q, 0);
    int const isPrim = fsl_stmt_g_int32(&q, 1);
    /* This block is only hit a couple of times during a fresh rebuild (empty mlink/plink
       tables), but many times on a rebuilds if those tables are not emptied in advance? */
    assert(cid>0);
    rc = fsl_mlink_add(f, d->rid, d, cid, NULL, isPrim ? true : false);
  }
  if(FSL_RC_STEP_DONE==rc) rc = 0;
  fsl_stmt_finalize(&q);
  if(rc) goto end;
  if( !d->P.used ){
    /* For root files (files without parents) add mlink entries
       showing all content as new.

       Historically, fossil has been unable to create such checkins
       because the initial checkin has no files.
    */
    int const isPublic = !fsl_content_is_private(f, d->rid);
    for(i=0; !rc && (i<d->F.used); ++i){
      fsl_card_F const * fc = F_at(&d->F, i);
      rc = fsl_mlink_add_one(f, 0, 0, d->rid, fc->uuid, fc->name, 0,
                             isPublic, 1, fc->perm);
    }
  }
  end:
  return rc;
}

/**
   Applies the value of a "parent" tag (reparent) to the given
   artifact id. zTagVal must be the value of a parent tag (a list of
   full UUIDs). This is only to be run as part of fsl_crosslink_end().

   Returns 0 on success.

   POTENTIAL fixme: perhaps return without side effects if rid is not
   found (like fossil(1) does). That said, this step is only run after
   crosslinking and would only result in a not-found if the tagxref
   table contents is out of date.

   POTENTIAL fixme: fail without error if the tag value is malformed,
   under the assumption that the tag was intended for some purpose
   other than reparenting.
*/
static int fsl_crosslink_reparent(fsl_cx * f, fsl_id_t rid, char const *zTagVal){
  int rc = 0;
  char * zDup = 0;
  char * zPos;
  fsl_size_t maxP, nP = 0;
  fsl_deck d = fsl_deck_empty;
  fsl_list fakeP = fsl_list_empty
    /* fake P-card for purposes of passing the reparented deck through
       fsl_deck_add_checkin_linkages() */;
  maxP = (fsl_strlen(zTagVal)+1) / (FSL_STRLEN_SHA1+1);
  if(!maxP) return FSL_RC_RANGE;
  rc = fsl_list_reserve(&fakeP, maxP);
  if(rc) return rc;
  zDup = fsl_strdup(zTagVal);
  if(!zDup){
    rc = FSL_RC_OOM;
    goto end;
  }
  /* Split zTagVal into list of parent IDs... */
  for( nP = 0, zPos = zDup; *zPos; ){
    char const * zBegin = zPos;
    for( ; *zPos && ' '!=*zPos; ++zPos){}
    if(' '==*zPos){
      *zPos = 0;
      ++zPos;
    }
    if(!fsl_is_uuid(zBegin)){
      rc = fsl_cx_err_set(f, FSL_RC_RANGE,
                          "Invalid value [%s] in reparent tag value "
                          "[%s] for rid %"FSL_ID_T_PFMT".",
                          zBegin, zTagVal, rid);
      goto end;
    }
    fakeP.list[nP++] = (void *)zBegin;
  }
  assert(!rc);
  fakeP.used = nP;
  rc = fsl_deck_load_rid(f, &d, rid, FSL_SATYPE_ANY);
  if(rc) goto end;
  switch(d.type){
    case FSL_SATYPE_CHECKIN:
    case FSL_SATYPE_TECHNOTE:
    case FSL_SATYPE_WIKI:
    case FSL_SATYPE_FORUMPOST:
      break;
    default:
      rc = fsl_cx_err_set(f, FSL_RC_TYPE, "Invalid deck type (%s) "
                          "for use with the 'parent' tag.",
                          fsl_satype_cstr(d.type));
      goto end;
  }
  assert(d.rid==rid);
  assert(d.f);
  fsl_db * const db = fsl_cx_db_repo(f);
  rc = fsl_db_exec_multi(db,
                         "DELETE FROM plink WHERE cid=%"FSL_ID_T_PFMT";"
                         "DELETE FROM mlink WHERE mid=%"FSL_ID_T_PFMT";",
                         rid, rid);
  if(rc) goto end;
  fsl_list const origP = d.P;
  d.P = fakeP;
  rc = fsl_deck_add_checkin_linkages(&d, NULL);
  d.P = origP;
  fsl_deck_finalize(&d);

  end:
  fsl_list_reserve(&fakeP, 0);
  fsl_free(zDup);
  return rc;
}

/**
   Inserts plink entries for FORUM, WIKI, and TECHNOTE manifests. May
   assert for other manifest types. If a parent entry exists, it also
   propagates any tags for that parent. This is a no-op if
   the deck has no parents.
*/
static int fsl_deck_crosslink_fwt_plink(fsl_deck * d){
  int i;
  fsl_id_t parentId = 0;
  fsl_db * db;
  int rc = 0;
  assert(d->type==FSL_SATYPE_WIKI ||
         d->type==FSL_SATYPE_FORUMPOST ||
         d->type==FSL_SATYPE_TECHNOTE);
  assert(d->f);
  assert(d->rid>0);
  if(!d->P.used) return rc;
  db = fsl_cx_db_repo(d->f);
  fsl_phantom_e const fantomMode = fsl_content_is_private(d->f, d->rid)
    ? FSL_PHANTOM_PRIVATE : FSL_PHANTOM_PUBLIC;
  for(i=0; 0==rc && i<(int)d->P.used; ++i){
    fsl_id_t const pid = fsl_uuid_to_rid2(d->f, (char const *)d->P.list[i],
                                          fantomMode);
    if(0==i) parentId = pid;
    rc = fsl_db_exec_multi(db,
                           "INSERT OR IGNORE INTO plink"
                           "(pid, cid, isprim, mtime, baseid)"
                           "VALUES(%"FSL_ID_T_PFMT", %"FSL_ID_T_PFMT", "
                           "%d, %"FSL_JULIAN_T_PFMT", NULL)",
                           pid, d->rid, i==0, d->D);
  }
  if(!rc && parentId){
    rc = fsl_tag_propagate_all(d->f, parentId);
  }
  return rc;
}


/**
   Overrideable crosslink listener which updates the timeline for
   attachment records.
*/
static int fsl_deck_xlink_f_attachment(fsl_deck * d, void * state){
  if(FSL_SATYPE_ATTACHMENT!=d->type) return 0;
  int rc;
  fsl_db * db;
  fsl_buffer comment = fsl_buffer_empty;
  const char isAdd = (d->A.src && *d->A.src) ? 1 : 0;
  char attachToType = 'w'
    /* Assume wiki until we know otherwise, keeping in mind that the
       d->A.tgt might not yet be in the blob table, in which case
       we are unable to know, for certain, what the target is.
       That only affects the timeline (event table), though, not
       the crosslinking of the attachment itself. */;
  db = fsl_cx_db_repo(d->f);
  assert(db);
  if(fsl_is_uuid(d->A.tgt)){
    if( fsl_db_exists(db, "SELECT 1 FROM tag WHERE tagname='tkt-%q'",
                      d->A.tgt)){
      attachToType = 't' /* attach to a known ticket */;
    }else if( fsl_db_exists(db, "SELECT 1 FROM tag WHERE tagname='event-%q'",
                            d->A.tgt)){
      attachToType = 'e' /* attach to a known technote (event) */;
    }
  }
  if('w'==attachToType){
    /* Attachment applies to a wiki page */
    if(isAdd){
      rc = fsl_buffer_appendf(&comment,
                              "Add attachment \"%h\" "
                              "to wiki page [%h]",
                              d->A.name, d->A.tgt);
    }else{
      rc = fsl_buffer_appendf(&comment,
                              "Delete attachment \"%h\" "
                              "from wiki page [%h]",
                              d->A.name, d->A.tgt);
    }
  }else if('e' == attachToType){/*technote*/
    if(isAdd){
      rc = fsl_buffer_appendf(&comment,
                              "Add attachment [/artifact/%!S|%h] to "
                              "tech note [/technote/%!S|%S]",
                              d->A.src, d->A.name, d->A.tgt, d->A.tgt);
    }else{
      rc = fsl_buffer_appendf(&comment,
                              "Delete attachment \"/artifact/%!S|%h\" "
                              "from tech note [/technote/%!S|%S]",
                              d->A.name, d->A.name, d->A.tgt,
                              d->A.tgt);
    }
  }else{
    /* Attachment applies to a ticket */
    if(isAdd){
      rc = fsl_buffer_appendf(&comment,
                              "Add attachment [/artifact/%!S|%h] "
                              "to ticket [%!S|%S]",
                              d->A.src, d->A.name, d->A.tgt, d->A.tgt);
    }else{
      rc = fsl_buffer_appendf(&comment,
                              "Delete attachment \"%h\" "
                              "from ticket [%!S|%S]",
                              d->A.name, d->A.tgt, d->A.tgt);
    }
  }
  if(!rc){
    rc = fsl_db_exec(db,
                     "REPLACE INTO event(type,mtime,objid,user,comment)"
                     "VALUES("
                     "'%c',%"FSL_JULIAN_T_PFMT",%"FSL_ID_T_PFMT","
                     "%Q,%B)",
                     attachToType, d->D, d->rid, d->U, &comment);
  }
  fsl_buffer_clear(&comment);
  return rc;
}

/**
   Overrideable crosslink listener which updates the timeline for
   checkin records.
*/
static int fsl_deck_xlink_f_checkin(fsl_deck * d, void * state){
  if(FSL_SATYPE_CHECKIN!=d->type) return 0;
  int rc;
  fsl_db * db;
  db = fsl_cx_db_repo(d->f);
  assert(db);
  rc = fsl_db_exec(db,
       "REPLACE INTO event(type,mtime,objid,user,comment,"
       "bgcolor,euser,ecomment,omtime)"
       "VALUES('ci',"
       "  coalesce(" /*mtime*/
       "    (SELECT julianday(value) FROM tagxref "
       "      WHERE tagid=%d AND rid=%"FSL_ID_T_PFMT
       "    ),"
       "    %"FSL_JULIAN_T_PFMT""
       "  ),"
       "  %"FSL_ID_T_PFMT","/*objid*/
       "  %Q," /*user*/
#if 1
       "  %Q," /*comment. No, the comment _field_. */
#else
       /* just for testing... */
       "  'xlink: %q'," /*comment. No, the comment _field_. */
#endif
       "  (SELECT value FROM tagxref " /*bgcolor*/
       "    WHERE tagid=%d AND rid=%"FSL_ID_T_PFMT
       "    AND tagtype>0"
       "  ),"
       "  (SELECT value FROM tagxref " /*euser*/
       "    WHERE tagid=%d AND rid=%"FSL_ID_T_PFMT
       "  ),"
       "  (SELECT value FROM tagxref " /*ecomment*/
       "    WHERE tagid=%d AND rid=%"FSL_ID_T_PFMT
       "  ),"
       "  %"FSL_JULIAN_T_PFMT/*omtime*/
       /* RETURNING coalesce(ecomment,comment)
          see comments below about zCom */
       ")",
       /* The casts here are to please the va_list. */
       (int)FSL_TAGID_DATE, d->rid, d->D,
       d->rid, d->U, d->C,
       (int)FSL_TAGID_BGCOLOR, d->rid,
       (int)FSL_TAGID_USER, d->rid,
       (int)FSL_TAGID_COMMENT, d->rid, d->D
  );
  return fsl_cx_uplift_db_error2(d->f, db, rc);
}

static int fsl_deck_xlink_f_control(fsl_deck * d, void * state){
  if(FSL_SATYPE_CONTROL!=d->type) return 0;
  /*
    Create timeline event entry for all tags in this control
    construct. Note that we are using a lot of historical code which
    hard-codes english-lanuage text and links which only work in
    fossil(1). i would prefer to farm this out to a crosslink
    callback, and provide a default implementation which more or
    less mimics fossil(1).
  */
  int rc = 0;
  fsl_buffer comment = fsl_buffer_empty;
  fsl_size_t i;
  const char *zName;
  const char *zValue;
  const char *zUuid;
  int branchMove = 0;
  int const uuidLen = 8;
  fsl_card_T const * tag = NULL;
  fsl_card_T const * prevTag = NULL;
  fsl_list const * li = &d->T;
  fsl_db * const db = fsl_cx_db_repo(d->f);
  double mtime = (d->D>0)
    ? d->D
    : fsl_db_julian_now(db);
  assert(db);
  /**
     Reminder to self: fossil(1) has a comment here:

     // Next loop expects tags to be sorted on UUID, so sort it.
     qsort(p->aTag, p->nTag, sizeof(p->aTag[0]), tag_compare);

     That sort plays a role in hook code execution and is needed to
     avoid duplicate hook execution in some cases. libfossil
     outsources that type of thing to crosslink callbacks, though,
     so we won't concern ourselves with it here. We also don't
     really want to modify the deck during crosslinking. The only
     reason the deck is not const in this routine is because of the
     fsl_deck::F::cursor bits inherited from fossil(1), largely
     worth its cost except that many routines can no longer be
     const. Shame C doesn't have C++'s "mutable" keyword.

     That said, sorting by UUID would have a nice side-effect on the
     output of grouping tags by the UUID they tag. So far
     (201404) such groups of tags have not appeared in the wild
     because fossil(1) has no mechanism for creating them.
  */
  for( i = 0; !rc && (i < li->used); ++i, prevTag = tag){
    char isProp = 0, isAdd = 0, isCancel = 0;
    tag = (fsl_card_T const *)li->list[i];
    zUuid = tag->uuid;
    if(!zUuid /*tag on self*/) continue;
    if( i==0 || 0!=fsl_uuidcmp(tag->uuid, prevTag->uuid)){
      rc = fsl_buffer_appendf(&comment,
                              " Edit [%.*s]:", uuidLen, zUuid);
      branchMove = 0;
    }
    if(rc) goto end;
    isProp = FSL_TAGTYPE_PROPAGATING==tag->type;
    isAdd = FSL_TAGTYPE_ADD==tag->type;
    isCancel = FSL_TAGTYPE_CANCEL==tag->type;
    assert(isProp || isAdd || isCancel);
    zName = tag->name;
    zValue = tag->value;
    if( isProp && 0==fsl_strcmp(zName, "branch")){
      rc = fsl_buffer_appendf(&comment,
                              " Move to branch %s"
                              "[/timeline?r=%h&nd&dp=%.*s | %h].",
                              zValue, zValue, uuidLen, zUuid, zValue);
      branchMove = 1;
    }else if( isProp && fsl_strcmp(zName, "bgcolor")==0 ){
      rc = fsl_buffer_appendf(&comment,
                              " Change branch background color to \"%h\".", zValue);
    }else if( isAdd && fsl_strcmp(zName, "bgcolor")==0 ){
      rc = fsl_buffer_appendf(&comment,
                              " Change background color to \"%h\".", zValue);
    }else if( isCancel && fsl_strcmp(zName, "bgcolor")==0 ){
      rc = fsl_buffer_appendf(&comment, " Cancel background color.");
    }else if( isAdd && fsl_strcmp(zName, "comment")==0 ){
      rc = fsl_buffer_appendf(&comment, " Edit check-in comment.");
    }else if( isAdd && fsl_strcmp(zName, "user")==0 ){
      rc = fsl_buffer_appendf(&comment, " Change user to \"%h\".", zValue);
    }else if( isAdd && fsl_strcmp(zName, "date")==0 ){
      rc = fsl_buffer_appendf(&comment, " Timestamp %h.", zValue);
    }else if( isCancel && memcmp(zName, "sym-",4)==0 ){
      if( !branchMove ){
        rc = fsl_buffer_appendf(&comment, " Cancel tag %h.", zName+4);
      }
    }else if( isProp && memcmp(zName, "sym-",4)==0 ){
      if( !branchMove ){
        rc = fsl_buffer_appendf(&comment, " Add propagating tag \"%h\".", zName+4);
      }
    }else if( isAdd && memcmp(zName, "sym-",4)==0 ){
      rc = fsl_buffer_appendf(&comment, " Add tag \"%h\".", zName+4);
    }else if( isCancel && memcmp(zName, "sym-",4)==0 ){
      rc = fsl_buffer_appendf(&comment, " Cancel tag \"%h\".", zName+4);
    }else if( isAdd && fsl_strcmp(zName, "closed")==0 ){
      rc = fsl_buffer_append(&comment, " Marked \"Closed\"", -1);
      if( !rc && zValue && *zValue ){
        rc = fsl_buffer_appendf(&comment, " with note \"%h\"", zValue);
      }
      if(!rc) rc = fsl_buffer_append(&comment, ".", 1);
    }else if( isCancel && fsl_strcmp(zName, "closed")==0 ){
      rc = fsl_buffer_append(&comment, " Removed the \"Closed\" mark", -1);
      if( !rc && zValue && *zValue ){
        rc = fsl_buffer_appendf(&comment, " with note \"%h\"", zValue);
      }
      if(!rc) rc = fsl_buffer_append(&comment, ".", 1);
    }else {
      if( isCancel ){
        rc = fsl_buffer_appendf(&comment, " Cancel \"%h\"", zName);
      }else if( isAdd ){
        rc = fsl_buffer_appendf(&comment, " Add \"%h\"", zName);
      }else{
        assert(isProp);
        rc = fsl_buffer_appendf(&comment, " Add propagating \"%h\"", zName);
      }
      if(rc) goto end;
      if( zValue && zValue[0] ){
        rc = fsl_buffer_appendf(&comment, " with value \"%h\".", zValue);
      }else{
        rc = fsl_buffer_append(&comment, ".", 1);
      }
    }
  } /* foreach tag loop */
  if(!rc){
    /* TODO: cached statement */
    rc = fsl_db_exec(db,
                     "REPLACE INTO event"
                     "(type,mtime,objid,user,comment) "
                     "VALUES('g',"
                     "%"FSL_JULIAN_T_PFMT","
                     "%"FSL_ID_T_PFMT","
                     "%Q,%Q)",
                     mtime, d->rid, d->U,
                     (comment.used>1)
                     ? (fsl_buffer_cstr(&comment)
                        +1/*leading space on all entries*/)
                     : NULL);
  }

  end:
  fsl_buffer_clear(&comment);
  return rc;

}

static int fsl_deck_xlink_f_forum(fsl_deck * d, void * state){
  if(FSL_SATYPE_FORUMPOST!=d->type) return 0;
  int rc = 0;
  fsl_db * const db = fsl_cx_db_repo(d->f);
  assert(db);
  fsl_cx * const f = d->f;
  fsl_id_t const froot = d->G ? fsl_uuid_to_rid(f, d->G) : d->rid;
  fsl_id_t const fprev = d->P.used ? fsl_uuid_to_rid(f, (char const *)d->P.list[0]): 0;
  fsl_id_t const firt = d->I ? fsl_uuid_to_rid(f, d->I) : 0;
  if( 0==firt ){
    /* This is the start of a new thread, either the initial entry
    ** or an edit of the initial entry. */
    const char * zTitle = d->H;
    const char * zFType;
    if(!zTitle || !*zTitle){
      zTitle = "(Deleted)";
    }
    zFType = fprev ? "Edit" : "Post";
    /* FSL-MISSING:
       assert( manifest_event_triggers_are_enabled ); */
    rc = fsl_db_exec_multi(db,
        "REPLACE INTO event(type,mtime,objid,user,comment)"
        "VALUES('f',%"FSL_JULIAN_T_PFMT",%" FSL_ID_T_PFMT
        ",%Q,'%q: %q')",
        d->D, d->rid, d->U, zFType, zTitle);
    if(rc) goto dberr;
      /*
      ** If this edit is the most recent, then make it the title for
      ** all other entries for the same thread
      */
    if( !fsl_db_exists(db,"SELECT 1 FROM forumpost "
                       "WHERE froot=%" FSL_ID_T_PFMT " AND firt=0"
                       " AND fpid!=%" FSL_ID_T_PFMT
                       " AND fmtime>%"FSL_JULIAN_T_PFMT,
                       froot, d->rid, d->D)){
        /* This entry establishes a new title for all entries on the thread */
      rc = fsl_db_exec_multi(db,
          "UPDATE event"
          " SET comment=substr(comment,1,instr(comment,':')) || ' %q'"
          " WHERE objid IN (SELECT fpid FROM forumpost WHERE froot=% " FSL_ID_T_PFMT ")",
          zTitle, froot);
      if(rc) goto dberr;
    }
  }else{
      /* This is a reply to a prior post.  Take the title from the root. */
    char const * zFType = 0;
    char * zTitle = fsl_db_g_text(
           db, 0, "SELECT substr(comment,instr(comment,':')+2)"
           "  FROM event WHERE objid=%"FSL_ID_T_PFMT, froot);
    if( zTitle==0 ){
      zTitle = fsl_strdup("<i>Unknown</i>");
      if(!zTitle){
        rc = FSL_RC_OOM;
        goto end;
      }
    }
    if( !d->W.used ){
      zFType = "Delete reply";
    }else if( fprev ){
      zFType = "Edit reply";
    }else{
      zFType = "Reply";
    }
    /* FSL-MISSING:
       assert( manifest_event_triggers_are_enabled ); */
    rc = fsl_db_exec_multi(db,
        "REPLACE INTO event(type,mtime,objid,user,comment)"
        "VALUES('f',%"FSL_JULIAN_T_PFMT
        ",%"FSL_ID_T_PFMT",%Q,'%q: %q')",
        d->D, d->rid, d->U, zFType, zTitle);
    fsl_free(zTitle);
    if(rc) goto end;
    if( d->W.used ){
      /* FSL-MISSING:
         backlink_extract(&d->W, d->N, d->rid, BKLNK_FORUM, d->D, 1); */
    }
  }
  end:
  return rc;
  dberr:
  assert(rc);
  assert(db->error.code);
  return fsl_cx_uplift_db_error(f, db);
}


static int fsl_deck_xlink_f_technote(fsl_deck * d, void * state){
  if(FSL_SATYPE_TECHNOTE!=d->type) return 0;
  char buf[FSL_STRLEN_K256 + 7 /* event-UUID\0 */] = {0};
  fsl_id_t tagid;
  char const * zTag;
  int rc = 0;
  fsl_cx * const f = d->f;
  fsl_db * const db = fsl_cx_db_repo(d->f);
  fsl_snprintf(buf, sizeof(buf), "event-%s", d->E.uuid);
  zTag = buf;
  tagid = fsl_tag_id( f, zTag, 1 );
  if(tagid<=0){
    return f->error.code ? f->error.code :
      fsl_cx_err_set(f, FSL_RC_RANGE,
                     "Got unexpected RID (%"FSL_ID_T_PFMT") "
                     "for tag [%s].",
                     tagid, zTag);
  }
  fsl_id_t const subsequent
    = fsl_db_g_id(db, 0,
                  "SELECT rid FROM tagxref"
                  " WHERE tagid=%"FSL_ID_T_PFMT
                  " AND mtime>=%"FSL_JULIAN_T_PFMT
                  " AND rid!=%"FSL_ID_T_PFMT
                  " ORDER BY mtime",
                  tagid, d->D, d->rid);
  if(subsequent<0){
    rc = fsl_cx_uplift_db_error(d->f, db);
  }else{
    rc = fsl_db_exec(db,
                     "REPLACE INTO event("
                     "type,mtime,"
                     "objid,tagid,"
                     "user,comment,bgcolor"
                     ")VALUES("
                     "'e',%"FSL_JULIAN_T_PFMT","
                     "%"FSL_ID_T_PFMT",%"FSL_ID_T_PFMT","
                     "%Q,%Q,"
                     "  (SELECT value FROM tagxref WHERE "
                     "   tagid=%d"
                     "   AND rid=%"FSL_ID_T_PFMT")"
                     ");",
                     d->E.julian, d->rid, tagid,
                     d->U, d->C, 
                     (int)FSL_TAGID_BGCOLOR, d->rid);
  }
  return rc;
}

static int fsl_deck_xlink_f_wiki(fsl_deck * d, void * state){
  if(FSL_SATYPE_WIKI!=d->type) return 0;
  int rc;
  char const * zWiki;
  fsl_size_t nWiki = 0;
  char cPrefix = 0;
  char * zTag = fsl_mprintf("wiki-%s", d->L);
  if(!zTag) return FSL_RC_OOM;
  fsl_id_t const tagid = fsl_tag_id( d->f, zTag, 1 );
  if(tagid<=0){
    rc = fsl_cx_err_set(d->f, FSL_RC_ERROR,
                        "Tag [%s] must have been added by main wiki crosslink step.",
                        zTag);
    goto end;
  }
  /* Some of this is duplicated in the main wiki crosslinking code :/. */
  zWiki = d->W.used ? fsl_buffer_cstr(&d->W) : "";
  while( *zWiki && fsl_isspace(*zWiki) ){
    ++zWiki;
    /* Historical behaviour: strip leading spaces. */
  }
  /* As of late 2020, fossil changed the conventions for how wiki
     entries are to be added to the timeline. They requrie a prefix
     character which tells the timeline display and email notification
     generator code what type of change this is: create/update/delete */
  nWiki = fsl_strlen(zWiki);
  if(!nWiki) cPrefix = '-';
  else if( !d->P.used ) cPrefix = '+';
  else cPrefix = ':';
  fsl_db * const db = fsl_cx_db_repo(d->f);
  rc = fsl_db_exec(db,
                   "REPLACE INTO event(type,mtime,objid,user,comment) "
                   "VALUES('w',%"FSL_JULIAN_T_PFMT
                   ",%"FSL_ID_T_PFMT",%Q,'%c%q%q%q');",
                   d->D, d->rid, d->U, cPrefix, d->L,
                   ((d->C && *d->C) ? ": " : ""),
                   ((d->C && *d->C) ? d->C : ""));
  /* Note that wiki pages optionally support d->C (change comment),
     but it's historically unused because it was a late addition to
     the artifact format and is not supported by older fossil
     versions. */
  rc = fsl_cx_uplift_db_error2(d->f, db, rc);
  end:
  fsl_free(zTag);
  return rc;
}


/** @internal

    Installs the core overridable crosslink listeners. "The plan" is
    to do all updates to the event (timeline) table via these
    crosslinkers and perform the core, UI-agnostic, crosslinking bits
    in the internal fsl_deck_crosslink_XXX() functions. That should
    allow clients to override how the timeline is updated without
    requiring them to understand the rest of the required schema
    updates.
*/
int fsl_cx_install_timeline_crosslinkers(fsl_cx *f){
  int rc;
  assert(!f->xlinkers.used);
  assert(!f->xlinkers.list);
  rc = fsl_xlink_listener(f, "fsl/attachment/timeline",
                          fsl_deck_xlink_f_attachment, 0);
  if(!rc) rc = fsl_xlink_listener(f, "fsl/checkin/timeline",
                          fsl_deck_xlink_f_checkin, 0);
  if(!rc) rc = fsl_xlink_listener(f, "fsl/control/timeline",
                          fsl_deck_xlink_f_control, 0);
  if(!rc) rc = fsl_xlink_listener(f, "fsl/forumpost/timeline",
                          fsl_deck_xlink_f_forum, 0);
  if(!rc) rc = fsl_xlink_listener(f, "fsl/technote/timeline",
                          fsl_deck_xlink_f_technote, 0);
  if(!rc) rc = fsl_xlink_listener(f, "fsl/wiki/timeline",
                          fsl_deck_xlink_f_wiki, 0);
  return rc;
}


static int fsl_deck_crosslink_checkin(fsl_deck * const d,
                                      fsl_id_t *parentid ){
  int rc = 0;
  fsl_cx * const f = d->f;
  fsl_db * const db = fsl_cx_db_repo(f);

  /* TODO: convert these queries to cached statements, for
     the sake of rebuild and friends. And bind() doubles
     instead of %FSL_JULIAN_T_PFMT'ing them.
  */
  if(d->Q.used && fsl_db_table_exists(db, FSL_DBROLE_REPO,
                                      "cherrypick")){
    fsl_size_t i;
    for(i=0; i < d->Q.used; ++i){
      fsl_card_Q const * q = (fsl_card_Q const *)d->Q.list[i];
      rc = fsl_db_exec(db,
          "REPLACE INTO cherrypick(parentid,childid,isExclude)"
          " SELECT rid, %"FSL_ID_T_PFMT", %d"
          " FROM blob WHERE uuid=%Q",
          d->rid, q->type<0 ? 1 : 0, q->target
      );
      if(rc) goto end;
    }
  }
  if(!fsl_repo_has_mlink_mid(db, d->rid)){
    rc = fsl_deck_add_checkin_linkages(d, parentid);
    if(rc) goto end;
    /* FSL-MISSING:
       assert( manifest_event_triggers_are_enabled ); */
    rc = fsl_search_doc_touch(f, d->type, d->rid, 0);
    if(rc) goto end;
    /* If this is a delta-manifest, record the fact that this repository
       contains delta manifests, to free the "commit" logic to generate
       new delta manifests. */
    if(d->B.uuid){
      rc = fsl_cx_update_seen_delta_mf(f);
      if(rc) goto end;
    }
    assert(!rc);
  }/*!exists mlink*/
  end:
  if(rc && !f->error.code && db->error.code){
    fsl_cx_uplift_db_error(f, db);
  }
  return rc;
}

static int fsl_deck_crosslink_wiki(fsl_deck *d){
  char zLength[40] = {0};
  fsl_id_t prior = 0;
  char const * zWiki;
  fsl_size_t nWiki = 0;
  int rc;
  char * zTag = fsl_mprintf("wiki-%s", d->L);
  fsl_cx * const f = d->f;
  fsl_db * const db = fsl_cx_db_repo(f);
  if(!zTag){
    return FSL_RC_OOM;
  }
  assert(f && db);
  zWiki = d->W.used ? fsl_buffer_cstr(&d->W) : "";
  while( *zWiki && fsl_isspace(*zWiki) ){
    ++zWiki;
    /* Historical behaviour: strip leading spaces. */
  }
  nWiki = fsl_strlen(zWiki)
    /* Reminder: use strlen instead of d->W.used just in case that
       one contains embedded NULs in the content. "Shouldn't
       happen," but the API doesn't explicitly prohibit it.
    */;
  fsl_snprintf(zLength, sizeof(zLength), "%"FSL_SIZE_T_PFMT,
               (fsl_size_t)nWiki);
  rc = fsl_tag_insert(f, FSL_TAGTYPE_ADD, zTag, zLength,
                      d->rid, d->D, d->rid, NULL );
  if(rc) goto end;
  if(d->P.used){
    prior = fsl_uuid_to_rid(f, (const char *)d->P.list[0]);
  }
  if(prior>0){
    rc = fsl_content_deltify(f, prior, d->rid, 0);
    if(rc) goto end;
  }
  rc = fsl_search_doc_touch(f, d->type, d->rid, d->L);
  if(rc) goto end;
  if( f->cache.isCrosslinking ){
    rc = fsl_deck_crosslink_add_pending(f, 'w',d->L);
    if(rc) goto end;
  }else{
    /* FSL-MISSING:
       backlink_wiki_refresh(d->L); */
  }
  assert(0==rc);
  rc = fsl_deck_crosslink_fwt_plink(d);
  end:
  fsl_free(zTag);
  return rc;
}

static int fsl_deck_crosslink_attachment(fsl_deck * const d){
  int rc;
  fsl_cx * const f = d->f;
  fsl_db * const db = fsl_cx_db_repo(f);

  rc = fsl_db_exec(db,
                   /* REMINDER: fossil(1) uses INSERT here, but that
                      breaks libfossil crosslinking tests due to a
                      unique constraint violation on attachid. */
                   "REPLACE INTO attachment(attachid, mtime, src, target,"
                   "filename, comment, user) VALUES("
                   "%"FSL_ID_T_PFMT",%"FSL_JULIAN_T_PFMT","
                   "%Q,%Q,%Q,"
                   "%Q,%Q);",
                   d->rid, d->D,
                   d->A.src, d->A.tgt, d->A.name,
                   (d->C ? d->C : ""), d->U);
  if(!rc){
    rc = fsl_db_exec(db,
                   "UPDATE attachment SET isLatest = (mtime=="
                   "(SELECT max(mtime) FROM attachment"
                   "  WHERE target=%Q AND filename=%Q))"
                   " WHERE target=%Q AND filename=%Q",
                   d->A.tgt, d->A.name,
                   d->A.tgt, d->A.name);
  }
  return rc;  
}

static int fsl_deck_crosslink_cluster(fsl_deck * const d){
  /* Clean up the unclustered table... */
  fsl_size_t i;
  fsl_stmt * st = NULL;
  int rc;
  fsl_cx * const f = d->f;
  fsl_db * const db = fsl_cx_db_repo(f);

  rc = fsl_db_prepare_cached(db, &st,
                             "DELETE FROM unclustered WHERE rid=?"
                             "/*%s()*/",__func__);
  if(rc) return fsl_cx_uplift_db_error(f, db);
  assert(st);
  for( i = 0; i < d->M.used; ++i ){
    fsl_id_t mid;
    char const * uuid = (char const *)d->M.list[i];
    mid = fsl_uuid_to_rid(f, uuid);
    if(mid>0){
      fsl_stmt_bind_id(st, 1, mid);
      if(FSL_RC_STEP_DONE!=fsl_stmt_step(st)){
        rc = fsl_cx_uplift_db_error(f, db);
        break;
      }
      fsl_stmt_reset(st);
    }
  }
  fsl_stmt_cached_yield(st);
  return rc;
}

#if 0
static int fsl_deck_crosslink_control(fsl_deck *const d){
}
#endif

static int fsl_deck_crosslink_forum(fsl_deck * const d){
  int rc = 0;
  fsl_cx * const f = d->f;
  rc = fsl_repo_install_schema_forum(f);
  if(rc) return rc;
  fsl_db * const db = fsl_cx_db_repo(f);
  fsl_id_t const froot = d->G ? fsl_uuid_to_rid(f, d->G) : d->rid;
  fsl_id_t const fprev = d->P.used ? fsl_uuid_to_rid(f, (char const *)d->P.list[0]): 0;
  fsl_id_t const firt = d->I ? fsl_uuid_to_rid(f, d->I) : 0;
  assert(f && db);
  rc = fsl_db_exec_multi(db,
      "REPLACE INTO forumpost(fpid,froot,fprev,firt,fmtime)"
      "VALUES(%" FSL_ID_T_PFMT ",%" FSL_ID_T_PFMT ","
      "nullif(%" FSL_ID_T_PFMT ",0),"
      "nullif(%" FSL_ID_T_PFMT ",0),%"FSL_JULIAN_T_PFMT")",
      d->rid, froot, fprev, firt, d->D
  );
  rc = fsl_cx_uplift_db_error2(f, db, rc);
  if(!rc){
    rc = fsl_search_doc_touch(f, d->type, d->rid, 0);
  }
  if(!rc){
    rc = fsl_deck_crosslink_fwt_plink(d);
  }
  return rc;
}

static int fsl_deck_crosslink_technote(fsl_deck * const d){
  char buf[FSL_STRLEN_K256 + 7 /* event-UUID\0 */] = {0};
  char zLength[40] = {0};
  fsl_id_t tagid;
  fsl_id_t prior = 0, subsequent;
  char const * zWiki;
  char const * zTag;
  fsl_size_t nWiki = 0;
  int rc;
  fsl_cx * const f = d->f;
  fsl_db * const db = fsl_cx_db_repo(f);
  fsl_snprintf(buf, sizeof(buf), "event-%s", d->E.uuid);
  zTag = buf;
  tagid = fsl_tag_id( f, zTag, 1 );
  if(tagid<=0){
    rc = f->error.code ? f->error.code :
      fsl_cx_err_set(f, FSL_RC_RANGE,
                     "Got unexpected RID (%"FSL_ID_T_PFMT") "
                     "for tag [%s].",
                     tagid, zTag);
    goto end;
  }
  zWiki = d->W.used ? fsl_buffer_cstr(&d->W) : "";
  while( *zWiki && fsl_isspace(*zWiki) ){
    ++zWiki;
    /* Historical behaviour: strip leading spaces. */
  }
  nWiki = fsl_strlen(zWiki);
  fsl_snprintf( zLength, sizeof(zLength), "%"FSL_SIZE_T_PFMT,
                (fsl_size_t)nWiki);
  rc = fsl_tag_insert(f, FSL_TAGTYPE_ADD, zTag, zLength,
                      d->rid, d->D, d->rid, NULL );
  if(rc) goto end;
  if(d->P.used){
    prior = fsl_uuid_to_rid(f, (const char *)d->P.list[0]);
    if(prior<0){
      assert(f->error.code);
      rc = f->error.code;
      goto end;
    }
  }
  subsequent = fsl_db_g_id(db, 0,
  /* BUG: see:
     https://fossil-scm.org/forum/forumpost/c58fd8de53 */
                           "SELECT rid FROM tagxref"
                           " WHERE tagid=%"FSL_ID_T_PFMT
                           " AND mtime>=%"FSL_JULIAN_T_PFMT
                           " AND rid!=%"FSL_ID_T_PFMT
                           " ORDER BY mtime",
                           tagid, d->D, d->rid);
  if(subsequent<0){
    assert(db->error.code);
    rc = fsl_cx_uplift_db_error(f, db);
    goto end;
  }
  else if( prior > 0 ){
    rc = fsl_content_deltify(f, prior, d->rid, 0);
    if( !rc && !subsequent ){
      rc = fsl_db_exec(db,
                       "DELETE FROM event"
                       " WHERE type='e'"
                       "   AND tagid=%"FSL_ID_T_PFMT
                       "   AND objid IN"
                       " (SELECT rid FROM tagxref "
                       " WHERE tagid=%"FSL_ID_T_PFMT")",
                       tagid, tagid);
    }
  }
  if(rc) goto end;
  if( subsequent>0 ){
    rc = fsl_content_deltify(f, d->rid, subsequent, 0);
  }else{
    /* timeline update is deferred to another crosslink
       handler */
    rc = fsl_search_doc_touch(f, d->type, d->rid, 0);
    /* FSL-MISSING:
       assert( manifest_event_triggers_are_enabled ); */
  }
  if(!rc){
    rc = fsl_deck_crosslink_fwt_plink(d);
  }
  end:
  return rc;
}

static int fsl_deck_crosslink_ticket(fsl_deck * const d){
  int rc;
  fsl_cx * const f = d->f;
#if 0
  fsl_db * const db = fsl_cx_db_repo(f);
#endif
  /*
    TODO: huge block from manifest_crosslink().  A full port
    requires other infrastructure for collapsing relatively close
    time values into the same time for timeline purposes. i'd prefer
    to farm this out to a crosslink callback.  Even then, the future
    of tickets in libfossil is uncertain, but we should crosslink
    them so that repos stay compatible with fossil(1) without
    requiring a rebuild using fossil(1).
  */
  if(f->flags & FSL_CX_F_SKIP_UNKNOWN_CROSSLINKS){
    return 0;
  }
  rc = fsl_cx_err_set(f, FSL_RC_NYI,
                      "MISSING: a huge block of TICKET stuff from "
                      "manifest_crosslink(). It requires infrastructure "
                      "libfossil does not yet have.");
  return rc;
}

int fsl_deck_crosslink_one( fsl_deck * d ){
  int rc;
  if(!d || !d->f) return FSL_RC_MISUSE;
  rc = fsl_crosslink_begin(d->f);
  if(rc) return rc;
  rc = fsl_deck_crosslink(d);
  if(rc){
    fsl_db_transaction_rollback(fsl_cx_db_repo(d->f))
      /* Ignore result - keep existing error state */;
    d->f->cache.isCrosslinking = false;
  }else{
    assert(fsl_db_transaction_level(fsl_cx_db_repo(d->f)));
    rc = fsl_crosslink_end(d->f);
  }
  return rc;
}

int fsl_deck_crosslink( fsl_deck /* const */ * d ){
  int rc = 0;
  fsl_cx * f = d->f;
  fsl_db * db = f ? fsl_needs_repo(f) : NULL;
  fsl_id_t parentid = 0;
  fsl_int_t const rid = d->rid;
  if(!f) return FSL_RC_MISUSE;
  else if(rid<=0){
    return fsl_cx_err_set(f, FSL_RC_RANGE,
                          "Invalid RID for crosslink: %"FSL_ID_T_PFMT,
                          rid);
  }
  else if(!db) return FSL_RC_NOT_A_REPO;
  else if(!fsl_deck_has_required_cards(d)){
    assert(d->f->error.code);
    return d->f->error.code;
  }else if(f->cache.xlinkClustersOnly && (FSL_SATYPE_CLUSTER!=d->type)){
    /* is it okay to bypass the registered xlink listeners here?  The
       use case called for by this is not yet implemented in
       libfossil. */
    return 0;
  }
  if(FSL_SATYPE_CHECKIN==d->type){
    if(d->B.uuid && !d->B.baseline){
      rc = fsl_deck_baseline_fetch(d);
      if(rc) goto end;
      assert(d->B.baseline);
    }
  }
  rc = fsl_db_transaction_begin(db);
  if(rc) goto end;
  switch(d->type){
    case FSL_SATYPE_CHECKIN:
      rc = fsl_deck_crosslink_checkin(d, &parentid);
      break;
    case FSL_SATYPE_CLUSTER:
      rc = fsl_deck_crosslink_cluster(d);
      break;
    default:
      break;
  }
  if(rc) goto end;
  switch(d->type){
    case FSL_SATYPE_CONTROL:
    case FSL_SATYPE_CHECKIN:
    case FSL_SATYPE_TECHNOTE:
      rc = fsl_deck_crosslink_apply_tags(f, d, db, rid, parentid);
      break;
    default:
      break;
  }
  if(rc) goto end;
  switch(d->type){
    case FSL_SATYPE_WIKI:
      rc = fsl_deck_crosslink_wiki(d);
      break;
    case FSL_SATYPE_FORUMPOST:
      rc = fsl_deck_crosslink_forum(d);
      break;
    case FSL_SATYPE_TECHNOTE:
      rc = fsl_deck_crosslink_technote(d);
      break;
    case FSL_SATYPE_TICKET:
      rc = fsl_deck_crosslink_ticket(d);
      break;
    case FSL_SATYPE_ATTACHMENT:
      rc = fsl_deck_crosslink_attachment(d);
      break;
    /* FSL_SATYPE_CONTROL is handled above except for the timeline
       update, which is handled by a callback below */
    default:
      break;
  }
  if(rc) goto end;

  /* Call any crosslink callbacks... */
  if(f->xlinkers.list){
    fsl_size_t i;
    fsl_xlinker * xl = NULL;
    for( i = 0; !rc && (i < f->xlinkers.used); ++i ){
      xl = f->xlinkers.list+i;
      rc = xl->f( d, xl->state );
    }
    if(rc){
      assert(xl);
      if(!f->error.code){
        fsl_cx_err_set(f, rc, "Crosslink callback handler "
                       "'%s' failed with code %d (%s) for "
                       "artifact [%.12s].",
                       xl->name, rc, fsl_rc_cstr(rc),
                       d->uuid);
      }
    }
  }/*end crosslink callbacks*/
  end:
  if(!rc){
    rc = fsl_db_transaction_end(db, false);
  }else{
    if(db->error.code && !f->error.code){
      fsl_cx_uplift_db_error(f,db);
    }
    fsl_db_transaction_end(db, true);
  }
  return rc;
}/*end fsl_deck_crosslink()*/



/**
    Return true if z points to the first character after a blank line.
    Tolerate either \r\n or \n line endings. As this looks backwards
    in z, z must point to at least 3 characters past the beginning of
    a legal string.
 */
static bool fsl_after_blank_line(const char *z){
  if( z[-1]!='\n' ) return false;
  if( z[-2]=='\n' ) return true;
  if( z[-2]=='\r' && z[-3]=='\n' ) return true;
  return false;
}

/**
    Verifies that ca points to at least 35 bytes of memory
    which hold (at the end) a Z card and its hash value.
   
    Returns 0 if the string does not contain a Z card,
    a positive value if it can validate the Z card's hash,
    and a negative value on hash mismatch.
*/
static int fsl_deck_verify_Z_card(unsigned char const * ca, fsl_size_t n){
  if( n<35 ) return 0;
  if( ca[n-35]!='Z' || ca[n-34]!=' ' ) return 0;
  else{
    unsigned char digest[16];
    char hex[FSL_STRLEN_MD5+1];
    unsigned char const * zHash = ca+n-FSL_STRLEN_MD5-1;
    fsl_md5_cx md5 = fsl_md5_cx_empty;
    unsigned char const * zHashEnd =
      ca + n -
      2 /* 'Z ' */
      - FSL_STRLEN_MD5
      - 1 /* \n */;
    assert( 'Z' == (char)*zHashEnd );
    fsl_md5_update(&md5, ca, zHashEnd-ca);
    fsl_md5_final(&md5, digest);
    fsl_md5_digest_to_base16(digest, hex);
    return (0==memcmp(zHash, hex, FSL_STRLEN_MD5))
      ? 1
      : -1;
  }
}

void fsl_remove_pgp_signature(unsigned char const **pz, fsl_size_t *pn){
  unsigned char const *z = *pz;
  fsl_int_t n = (fsl_int_t)*pn;
  fsl_int_t i;
  if( n<59 || memcmp(z, "-----BEGIN PGP SIGNED MESSAGE-----", 34)!=0 ) return;
  for(i=34; i<n && !fsl_after_blank_line((char const *)(z+i)); i++){}
  if( i>=n ) return;
  z += i;
  n -= i;
  *pz = z;
  for(i=n-1; i>=0; i--){
    if( z[i]=='\n' && memcmp(&z[i],"\n-----BEGIN PGP SIGNATURE-", 25)==0 ){
      n = i+1;
      break;
    }
  }
  *pn = (fsl_size_t)n;
  return;
}


/**
    Internal helper for parsing manifests. Holds a source file (memory
    range) and gets updated by fsl_deck_next_token() and friends.
*/
struct fsl_src {
  /**
      First char of the next token.
   */
  unsigned char * z;
  /**
      One-past-the-end of the manifest.
   */
  unsigned char * zEnd;
  /**
      True if z points to the start of a new line.
   */
  char atEol;
};
typedef struct fsl_src fsl_src;
static const fsl_src fsl_src_empty = {NULL,NULL,0};

/**
   Return a pointer to the next token.  The token is zero-terminated.
   Return NULL if there are no more tokens on the current line.  If
   pLen is not NULL and this function returns non-NULL then *pLen is
   set to the byte length of the new token.
*/
static unsigned char *fsl_deck_next_token(fsl_src *p, fsl_size_t *pLen){
  unsigned char *z;
  unsigned char *zStart;
  int c;
  if( p->atEol ) return NULL;
  zStart = z = p->z;
  while( (c=(*z))!=' ' && c!='\n' ){ ++z; }
  *z = 0;
  p->z = &z[1];
  p->atEol = c=='\n';
  if( pLen ) *pLen = z - zStart;
  return zStart;
}

/**
    Return the card-type for the next card. Return 0 if there are no
    more cards or if we are not at the end of the current card.
 */
static unsigned char mf_next_card(fsl_src *p){
  unsigned char c;
  if( !p->atEol || p->z>=p->zEnd ) return 0;
  c = p->z[0];
  if( p->z[1]==' ' ){
    p->z += 2;
    p->atEol = 0;
  }else if( p->z[1]=='\n' ){
    p->z += 2;
    p->atEol = 1;
  }else{
    c = 0;
  }
  return c;
}

/**
    Internal helper for fsl_deck_parse(). Expects l to be an array of
    26 entries, representing the letters of the alphabet (A-Z), with a
    value of 0 if the card was not seen during parsing and a value >0
    if it was. Returns the deduced artifact type.  Returns
    FSL_SATYPE_ANY if the result is ambiguous.

    Note that we cannot reliably guess until we've seen at least 3
    cards. 2 cards is enough for most cases but can lead to
    FSL_SATYPE_CHECKIN being prematurely selected in one case.
   
    It should guess right for any legal manifests, but it does not go
    out of its way to detect incomplete/invalid ones.
 */
static fsl_satype_e fsl_deck_guess_type( const int * l ){
#if 0
  /* For parser testing only... */
  int i;
  assert(!l[26]);
  MARKER(("Cards seen during parse:\n"));
  for( i = 0; i < 26; ++i ){
    if(l[i]) putchar('A'+i);
  }
  putchar('\n');
#endif
  /*
     Now look for combinations of cards which will uniquely
     identify any syntactical legal combination of cards.
     
     A larger brain than mine could probably come up with a hash of
     l[] which could determine this in O(1). But please don't
     reimplement this as such unless mere mortals can maintain it -
     any performance gain is insignificant in the context of the
     underlying SCM/db operations.

     Note that the order of these checks is sometimes significant!
  */
#define L(X) l[X-'A']
  if(L('M')) return FSL_SATYPE_CLUSTER;
  else if(L('E')) return FSL_SATYPE_EVENT;
  else if(L('G') || L('H') || L('I')) return FSL_SATYPE_FORUMPOST;
  else if(L('L') || L('W')) return FSL_SATYPE_WIKI;
  else if(L('J') || L('K')) return FSL_SATYPE_TICKET;
  else if(L('A')) return FSL_SATYPE_ATTACHMENT;
  else if(L('B') || L('C') || L('F')
          || L('P') || L('Q') || L('R')) return FSL_SATYPE_CHECKIN;
  else if(L('D') && L('T') && L('U')) return FSL_SATYPE_CONTROL;
#undef L
  return FSL_SATYPE_ANY;
}

bool fsl_might_be_artifact(fsl_buffer const * src){
  unsigned const char * z = src->mem;
  fsl_size_t n = src->used;
  if(n<36) return 0;
  fsl_remove_pgp_signature(&z, &n);
  if(n<36) return 0;
  else if(z[0]<'A' || z[0]>'Z' || z[1]!=' '
          || z[n-35]!='Z'
          || z[n-34]!=' '
          || !fsl_validate16((const char *)z+n-33, FSL_STRLEN_MD5)){
    return 0;
  }
  return 1;
}

int fsl_deck_parse2(fsl_deck * d, fsl_buffer * src, fsl_id_t rid){
#ifdef ERROR
#  undef ERROR
#endif
#define ERROR(RC,MSG) do{ rc = (RC); zMsg = (MSG); goto bailout; } while(0)
#define SYNTAX(MSG) ERROR(rc ? rc : FSL_RC_SYNTAX,MSG)
  bool isRepeat = 0/* , hasSelfRefTag = 0 */;
  int rc = 0;
  fsl_src x = fsl_src_empty;
  char const * zMsg = NULL;
  fsl_id_bag * seen;
  char cType = 0, cPrevType = 0;
  unsigned char * z = src ? src->mem : NULL;
  fsl_size_t tokLen = 0;
  unsigned char * token;
  fsl_size_t n = z ? src->used : 0;
  unsigned char * uuid;
  double ts;
  int cardCount = 0;
  fsl_db * db;
  fsl_cx * f;
  fsl_error * err;
  int stealBuf = 0 /* gets incremented if we need to steal src->mem. */;
  unsigned nSelfTag = 0 /* number of T cards which refer to '*' (this artifact). */;
  unsigned nSimpleTag = 0 /* number of T cards with "+" prefix */;
  /*
    lettersSeen keeps track of the card letters we have seen so that
    we can then relatively quickly figure out what type of manifest we
    have parsed without having to inspect the card contents. Each
    index records a count of how many of that card we've seen.
  */
  int lettersSeen[27] = {0,0,0,0,0,0,0,0,0,0,
                              0,0,0,0,0,0,0,0,0,0,
                              0,0,0,0,0,0,0};
  if(!d->f || !z) return FSL_RC_MISUSE;
  /* Every control artifact ends with a '\n' character.  Exit early
     if that is not the case for this artifact. */
  f = d->f;
  err = &f->error;
  if(!*z || !n || ( '\n' != z[n-1]) ){
    return fsl_error_set(err, FSL_RC_SYNTAX, "%s.",
                         n ? "Not terminated with \\n"
                         : "Zero-length input");
  }
  else if(rid<0){
    return fsl_error_set(err, FSL_RC_RANGE,
                         "Invalid (negative) RID %"FSL_ID_T_PFMT
                         " for fsl_deck_parse()", rid);
  }
  db = fsl_cx_db_repo(f);
  seen = f ? &f->cache.mfSeen : NULL;
  if(seen){
    if((0==rid) || fsl_id_bag_contains(seen,rid)){
      isRepeat = 1;
    }else{
      isRepeat = 0;
      rc = fsl_id_bag_insert(seen, rid);
      if(rc){
        assert(FSL_RC_OOM==rc);
        return rc;
      }
    }
  }
  fsl_deck_clean(d);
  fsl_deck_init(f, d, FSL_SATYPE_ANY);
  /* We have to hash BEFORE parsing, as parsing modifies
     the input */
  if(rid){
    assert(rid>0);
    d->rid = rid;
    d->uuid = fsl_rid_to_uuid(f, rid);
    if(!d->uuid){
      rc = f->error.code ? f->error.code : FSL_RC_OOM;
      goto end;
    }
  }else if(db){
    rc = fsl_repo_blob_lookup(f, src, &d->rid, &d->uuid);
    if(FSL_RC_NOT_FOUND==rc){
      /* Non-fatal */
      rc = 0;
    }
  }else{
    fsl_buffer hash = fsl_buffer_empty;
    rc = fsl_cx_hash_buffer(f, false, src, &hash);
    if(rc){
      fsl_buffer_clear(&hash);
    }else{
      d->uuid = fsl_buffer_str(&hash);
    }
  }
  if(rc) return rc;

  /* legacy: not yet clear if we need this:
     if( !isRepeat ) g.parseCnt[0]++; */

  /*
    Verify that the first few characters of the artifact look like a
    control artifact.
  */
  if( !fsl_might_be_artifact(src) ){
    ERROR(FSL_RC_SYNTAX, "Content does not look like "
          "a structural artifact");
  }

  /*
    Strip off the PGP signature if there is one. Example of signed
    manifest:

    https://fossil-scm.org/index.html/artifact/28987096ac
  */
  {
    unsigned char const * zz = z;
    fsl_remove_pgp_signature(&zz, &n);
    z = (unsigned char *)zz;
  }

  /* Verify the Z card */
  if( fsl_deck_verify_Z_card(z, n) < 0 ){
    ERROR(FSL_RC_CONSISTENCY, "Z-card checksum mismatch");
  }

  /*
    Reminder: parsing modifies the input (to simplify the
    tokenization/parsing).

    As of mid-201403, we recycle as much as possible from the source
    buffer and take over ownership _if_ we do so.
  */
  /* Now parse, card by card... */
  x.z = z;
  x.zEnd = z+n;
  x.atEol= 1;

  /* Parsing helpers... */
#define TOKEN(DEFOS) tokLen=0; token = fsl_deck_next_token(&x,&tokLen);    \
  if(token && tokLen && (DEFOS)) fsl_bytes_defossilize(token, &tokLen)
#define TOKEN_EXISTS(MSG_IF_NOT) if(!token){ SYNTAX(MSG_IF_NOT); }(void)0
#define TOKEN_CHECKHEX(MSG) if(token && (int)tokLen!=fsl_is_uuid((char const *)token))\
  { SYNTAX(MSG); }
#define TOKEN_UUID(CARD) TOKEN_CHECKHEX("Malformed UUID in " #CARD "-card")
#define TOKEN_MD5(ERRMSG) if(!token || FSL_STRLEN_MD5!=(int)tokLen) \
  {SYNTAX(ERRMSG);}
  /**
     Reminder: we do not know the type of the manifest at this point,
     so all of the fsl_deck_add/set() bits below can't do their
     validation. We have to determine at parse-time (or afterwards)
     which type of deck it is based on the cards we've seen. We guess
     the type as early as possible to enable during-parse validation,
     and do a post-parse check for the legality of cards added before
     validation became possible.
   */
  
#define SEEN(CARD) lettersSeen[*#CARD - 'A']
  for( cPrevType=1; !rc && (0 < (cType = mf_next_card(&x)));
       cPrevType = cType ){
    ++cardCount;
    if(cType<cPrevType){
      if(d->E.uuid && 'N'==cType && 'P'==cPrevType){
        /* Workaround for a pair of historical fossil bugs
           which synergized to allow malformed technotes to
           be saved:
           https://fossil-scm.org/home/info/023fddeec4029306 */
      }else{
        SYNTAX("Cards are not in strict lexical order");
      }
    }
    assert(cType>='A' && cType<='Z');
    if(cType>='A' && cType<='Z'){
        ++lettersSeen[cType-'A'];
    }else{
      SYNTAX("Invalid card name");
    }
    switch(cType){
      /*
             A <filename> <target> ?<source>?
        
         Identifies an attachment to either a wiki page, a ticket, or
         a technote.  <source> is the artifact that is the attachment.
         <source> is omitted to delete an attachment.  <target> is the
         name of a wiki page, technote, or ticket to which that
         attachment is connected.
      */
      case 'A':{
        unsigned char * name, * src;
        if(1<SEEN(A)){
          ERROR(FSL_RC_RANGE,"Multiple A-cards");
        }
        TOKEN(1);
        TOKEN_EXISTS("Missing filename for A-card");
        name = token;
        if(!fsl_is_simple_pathname( (char const *)name, 0 )){
          SYNTAX("Invalid filename in A-card");
        }          
        TOKEN(1);
        TOKEN_EXISTS("Missing target name in A-card");
        uuid = token;
        TOKEN(0);
        TOKEN_UUID(A);
        src = token;
        d->A.name = (char *)name;
        d->A.tgt = (char *)uuid;
        d->A.src = (char *)src;
        ++stealBuf;
        /*rc = fsl_deck_A_set(d, (char const *)name,
          (char const *)uuid, (char const *)src);*/
        d->type = FSL_SATYPE_ATTACHMENT;
        break;
      }
      /*
            B <uuid>
        
         A B-line gives the UUID for the baseline of a delta-manifest.
      */
      case 'B':{
        if(d->B.uuid){
          SYNTAX("Multiple B-cards");
        }
        TOKEN(0);
        TOKEN_UUID(B);
        d->B.uuid = (char *)token;
        ++stealBuf;
        d->type = FSL_SATYPE_CHECKIN;
        /* rc = fsl_deck_B_set(d, (char const *)token); */
        break;
      }
      /*
             C <comment>
        
         Comment text is fossil-encoded.  There may be no more than
         one C line.  C lines are required for manifests, are optional
         for Events and Attachments, and are disallowed on all other
         control files.
      */
      case 'C':{
        if( d->C ){
          SYNTAX("more than one C-card");
        }
        TOKEN(1);
        TOKEN_EXISTS("Missing comment text for C-card");
        /* rc = fsl_deck_C_set(d, (char const *)token, (fsl_int_t)tokLen); */
        d->C = (char *)token;
        ++stealBuf;
        break;
      }
      /*
             D <timestamp>
        
         The timestamp should be ISO 8601.   YYYY-MM-DDtHH:MM:SS
         There can be no more than 1 D line.  D lines are required
         for all control files except for clusters.
      */
      case 'D':{
#define TOKEN_DATETIME(LETTER,MEMBER)                                     \
        if( d->MEMBER>0.0 ) { SYNTAX("More than one "#LETTER"-card"); } \
        TOKEN(0); \
        TOKEN_EXISTS("Missing date part of "#LETTER"-card"); \
        if(!fsl_str_is_date((char const *)token)){\
          SYNTAX("Malformed date part of "#LETTER"-card"); \
        } \
        if(!fsl_iso8601_to_julian((char const *)token, &ts)){   \
          SYNTAX("Cannot parse date from "#LETTER"-card"); \
        } (void)0

        TOKEN_DATETIME(D,D);
        rc = fsl_deck_D_set(d, ts);
        break;
      }
      /*
             E <timestamp> <uuid>
        
         An "event" card that contains the timestamp of the event in the 
         format YYYY-MM-DDtHH:MM:SS and a unique identifier for the event.
         The event timestamp is distinct from the D timestamp.  The D
         timestamp is when the artifact was created whereas the E timestamp
         is when the specific event is said to occur.
      */
      case 'E':{
        TOKEN_DATETIME(E,E.julian);
        TOKEN(0);
        TOKEN_EXISTS("Missing UUID part of E-card");
        TOKEN_UUID(E);
        d->E.julian = ts;
        d->E.uuid = (char *)token;
        ++stealBuf;
        d->type = FSL_SATYPE_EVENT;
        break;
      }
      /*
             F <filename> ?<uuid>? ?<permissions>? ?<old-name>?
        
         Identifies a file in a manifest.  Multiple F lines are
         allowed in a manifest.  F lines are not allowed in any other
         control file.  The filename and old-name are fossil-encoded.

         In delta manifests, deleted files are denoted by the 1-arg
         form. In baseline manifests, deleted files simply are not in
         the manifest.
      */
      case 'F':{
        char * name;
        char * perms = NULL;
        char * priorName = NULL;
        fsl_fileperm_e perm = FSL_FILE_PERM_REGULAR;
        fsl_card_F * fc = NULL;
        /**
           Basic tests with various repos have shown that the
           approximate number of F-cards in a manifest is rougly the
           manifest size/75. We'll use that as an initial alloc size.
        */
        rc = 0;
        if(!d->F.capacity){
          rc = fsl_card_F_list_reserve(&d->F, src->used/75+10);
        }
        TOKEN(0);
        TOKEN_EXISTS("Missing name for F-card");
        name = (char *)token;
        TOKEN(0);
        TOKEN_UUID(F);
        uuid = token;
        TOKEN(0);
        if(token){
          perms = (char *)token;
          switch(*perms){
            case 0:
              /* Some (maybe only 1) ancient fossil(1) artifact(s) have a trailing
                 space which triggers this. e.g.

                 https://fossil-scm.org/home/info/32b480faa3465591b8549bdfd889d62d7a8d16a8
              */
              break;
            case 'w': perm = FSL_FILE_PERM_REGULAR; break;
            case 'x': perm = FSL_FILE_PERM_EXE; break;
            case 'l': perm = FSL_FILE_PERM_LINK; break;
            default:
              /*MARKER(("Unmatched perms string character: %d / %c !", (int)*perms, *perms));*/
              assert(!"Unmatched perms string character!");
              ERROR(FSL_RC_ERROR,"Internal error: unmatched perms string character");
          }
          TOKEN(0);
          if(token) priorName = (char *)token;
        }
        fsl_bytes_defossilize( (unsigned char *)name, 0 );
        if(priorName) fsl_bytes_defossilize( (unsigned char *)priorName, 0 );
        if(fsl_is_reserved_fn(name, -1)){
          /* Some historical (pre-late-2020) manifests contain files
             they really shouldn't, like _FOSSIL_ and .fslckout.
             Since late 2020, fossil simply skips over these when
             parsing manifests, so we'll do the same. */
          break;
        }
        fc = rc ? 0 : fsl_card_F_list_push(&d->F);
        if(!fc){
          zMsg = "OOM";
          goto bailout;
        }
        ++stealBuf;
        assert(d->F.used>1
               ? (FSL_CARD_F_LIST_NEEDS_SORT & d->F.flags)
               : 1);
        fc->deckOwnsStrings = true;
        fc->name = name;
        fc->priorName = priorName;
        fc->perm = perm;
        fc->uuid = (fsl_uuid_str)uuid;
        d->type = FSL_SATYPE_CHECKIN;
        break;
      }
      /*
        G <uuid>
        
        A G-line gives the UUID for the thread root of a forum post.
      */
      case 'G':{
        if(d->G){
          SYNTAX("Multiple G-cards");
        }
        TOKEN(0);
        TOKEN_EXISTS("Missing UUID in G-card");
        TOKEN_UUID(G);
        d->G = (char*)token;
        ++stealBuf;
        d->type = FSL_SATYPE_FORUMPOST;
        break;
      }
     /*
         H <forum post title>
        
         H text is fossil-encoded.  There may be no more than one H
         line.  H lines are optional for forum posts and are
         disallowed on all other control files.
      */
      case 'H':{
        if( d->H ){
          SYNTAX("more than one H-card");
        }
        TOKEN(1);
        TOKEN_EXISTS("Missing text for H-card");
        d->H = (char *)token;
        ++stealBuf;
        d->type = FSL_SATYPE_FORUMPOST;
        break;
      }
      /*
        I <uuid>
        
        A I-line gives the UUID for the in-response-to UUID for 
        a forum post.
      */
      case 'I':{
        if(d->I){
          SYNTAX("Multiple I-cards");
        }
        TOKEN(0);
        TOKEN_EXISTS("Missing UUID in I-card");
        TOKEN_UUID(I);
        d->I = (char*)token;
        ++stealBuf;
        d->type = FSL_SATYPE_FORUMPOST;
        break;
      }
      /*
             J <name> ?<value>?
        
         Specifies a name value pair for ticket.  If the first character
         of <name> is "+" then the <value> is appended to any preexisting
         value.  If <value> is omitted then it is understood to be an
         empty string.
      */
      case 'J':{
        char const * field;
        bool isAppend = 0;
        TOKEN(1);
        TOKEN_EXISTS("Missing field name for J-card");
        field = (char const *)token;
        if('+'==*field){
          isAppend = 1;
          ++field;
        }
        TOKEN(1);
        rc = fsl_deck_J_add(d, isAppend, field,
                            (char const *)token);
        d->type = FSL_SATYPE_TICKET;
        break;
      }
      /*
            K <uuid>
        
         A K-line gives the UUID for the ticket which this control file
         is amending.
      */
      case 'K':{
        if(d->K){
          SYNTAX("Multiple K-cards");
        }
        TOKEN(0);
        TOKEN_EXISTS("Missing UUID in K-card");
        TOKEN_UUID(K);
        d->K = (char*)token;
        ++stealBuf;
        d->type = FSL_SATYPE_TICKET;
        break;
      }
      /*
             L <wikititle>
        
         The wiki page title is fossil-encoded.  There may be no more than
         one L line.
      */
      case 'L':{
        if(d->L){
          SYNTAX("Multiple L-cards");
        }
        TOKEN(1);
        TOKEN_EXISTS("Missing text for L-card");
        d->L = (char*)token;
        ++stealBuf;
        d->type = FSL_SATYPE_WIKI;
        break;
      }
      /*
            M <uuid>
        
         An M-line identifies another artifact by its UUID.  M-lines
         occur in clusters only.
      */
      case 'M':{
        TOKEN(0);
        TOKEN_EXISTS("Missing UUID for M-card");
        TOKEN_UUID(M);
        ++stealBuf;
        d->type = FSL_SATYPE_CLUSTER;
        rc = fsl_list_append(&d->M, token);
        if( !rc && d->M.used>1 &&
            fsl_strcmp((char const *)d->M.list[d->M.used-2],
                       (char const *)token)>=0 ){
          SYNTAX("M-card in the wrong order");
        }
        break;
      }
      /*
            N <uuid>
        
         An N-line identifies the mimetype of wiki or comment text.
      */
      case 'N':{
        if(1<SEEN(N)){
          ERROR(FSL_RC_RANGE,"Multiple N-cards");
        }
        TOKEN(0);
        TOKEN_EXISTS("Missing UUID on N-card");
        ++stealBuf;
        d->N = (char *)token;
        break;
      }

      /*
             P <uuid> ...
        
         Specify one or more other artifacts which are the parents of
         this artifact.  The first parent is the primary parent.  All
         others are parents by merge.
      */
      case 'P':{
        if(1<SEEN(P)){
          ERROR(FSL_RC_RANGE,"More than one P-card");
        }
        TOKEN(0);
#if 0
        /* The docs all claim that this card does not exist on the first
           manifest, but in fact it does exist but has no UUID,
           which is invalid per all the P-card docs. Skip this
           check (A) for the sake of manifest #1 and (B) because
           fossil(1) does it this way.
        */
        TOKEN_EXISTS("Missing primary parent UUID for P-card");
#endif
        while( token && !rc ){
          TOKEN_UUID(P);
          ++stealBuf;
          rc = fsl_list_append(&d->P, token);
          if(!rc){
            TOKEN(0);
          }
        }
        break;
      }
      /*
             Q (+|-)<uuid> ?<uuid>?
        
         Specify one or a range of checkins that are cherrypicked into
         this checkin ("+") or backed out of this checkin ("-").
      */
      case 'Q':{
        fsl_cherrypick_type_e qType = FSL_CHERRYPICK_INVALID;
        TOKEN(0);
        TOKEN_EXISTS("Missing target UUID for Q-card");
        switch((char)*token){
          case '-': qType = FSL_CHERRYPICK_BACKOUT; break;
          case '+': qType = FSL_CHERRYPICK_ADD; break;
          default:
            SYNTAX("Malformed target UUID in Q-card");
        }
        assert(qType);
        uuid = ++token; --tokLen;
        TOKEN_UUID(Q);
        TOKEN(0);
        if(token){
          TOKEN_UUID(Q);
        }
        d->type = FSL_SATYPE_CHECKIN;
        rc = fsl_deck_Q_add(d, qType, (char const *)uuid,
                            (char const *)token);
        break;
      }
      /*
             R <md5sum>
        
         Specify the MD5 checksum over the name and content of all files
         in the manifest.
      */
      case 'R':{
        if(1<SEEN(R)){
          ERROR(FSL_RC_RANGE,"More than one R-card");
        }
        TOKEN(0);
        TOKEN_EXISTS("Missing MD5 token in R-card");
        TOKEN_MD5("Malformed MD5 token in R-card");
        d->R = (char *)token;
        ++stealBuf;
        d->type = FSL_SATYPE_CHECKIN;
        break;
      }
      /*
            T (+|*|-)<tagname> <uuid> ?<value>?
        
         Create or cancel a tag or property.  The tagname is fossil-encoded.
         The first character of the name must be either "+" to create a
         singleton tag, "*" to create a propagating tag, or "-" to create
         anti-tag that undoes a prior "+" or blocks propagation of of
         a "*".
        
         The tag is applied to <uuid>.  If <uuid> is "*" then the tag is
         applied to the current manifest.  If <value> is provided then 
         the tag is really a property with the given value.
        
         Tags are not allowed in clusters.  Multiple T lines are allowed.
      */
      case 'T':{
        unsigned char * name, * value;
        fsl_tagtype_e tagType = FSL_TAGTYPE_INVALID;
        TOKEN(1);
        TOKEN_EXISTS("Missing name for T-card");
        name = token;
        if( fsl_validate16((char const *)&name[1],
                           fsl_strlen((char const *)&name[1])) ){
          /* Do not allow tags whose names look like a hash */
          SYNTAX("T-card name looks like a hexadecimal hash");
        }
        TOKEN(0);
        TOKEN_EXISTS("Missing UUID on T-card");
        if(fsl_is_uuid_len((int)tokLen)){
          TOKEN_UUID(T);
          uuid = token;
        }else if( 1==tokLen && '*'==(char)*token ){
          /* tag for the current artifact */
          ++nSelfTag;
          uuid = NULL;
        }else{
          SYNTAX("Malformed UUID in T-card");
        }
        TOKEN(1);
        value = token;
        switch(*name){
          case '*': tagType = FSL_TAGTYPE_PROPAGATING; break;
          case '+': tagType = FSL_TAGTYPE_ADD;
            ++nSimpleTag;
            break;
          case '-': tagType = FSL_TAGTYPE_CANCEL; break;
          default: SYNTAX("Malformed tag name");
        }
        ++name /* skip type marker byte */;
        /* Potential todo: add the order check from this commit:

        https://fossil-scm.org/index.html/info/55cacfcace
        */
        rc = fsl_deck_T_add(d, tagType, (fsl_uuid_cstr)uuid,
                            (char const *)name,
                            (char const *)value);
        break;
      }
      /*
             U ?<login>?
        
         Identify the user who created this control file by their
         login.  Only one U line is allowed.  Prohibited in clusters.
         If the user name is omitted, take that to be "anonymous".
      */
      case 'U':{
        if(d->U) SYNTAX("More than one U-card");
        TOKEN(1);
        if(token){
          /* rc = fsl_deck_U_set( d, (char const *)token, (fsl_int_t)tokLen ); */
          ++stealBuf;
          d->U = (char *)token;
        }else{
          rc = fsl_deck_U_set( d, "anonymous" );
        }
        break;
      }
      /*
             W <size>
        
         The next <size> bytes of the file contain the text of the wiki
         page.  There is always an extra \n before the start of the next
         record.
      */
      case 'W':{
        fsl_size_t wlen;
        if(d->W.used){
          SYNTAX("More than one W-card");
        }
        TOKEN(0);
        TOKEN_EXISTS("Missing size token for W-card");
        wlen = fsl_str_to_size((char const *)token);
        if((fsl_size_t)-1==wlen){
          ERROR(FSL_RC_RANGE,"Wiki size token is invalid");
        }
        if( (&x.z[wlen+1]) > x.zEnd){
          SYNTAX("Not enough content after W-card");
        }
        rc = fsl_buffer_append(&d->W, x.z, wlen);
        if(rc) goto bailout;
        x.z += wlen;
        if( '\n' != x.z[0] ){
          SYNTAX("W-card content not \\n terminated");
        }
        x.z[0] = 0;
        ++x.z;
        break;
      }
      /*
             Z <md5sum>
        
         MD5 checksum on this control file.  The checksum is over all
         lines (other than PGP-signature lines) prior to the current
         line.  This must be the last record.
        
         This card is required for all control file types except for
         Manifest. It is not required for manifest only for historical
         compatibility reasons.
      */
      case 'Z':{
        /* We validated the Z card first. We cannot compare against
           the original blob now because we've modified it.
        */
        goto end;
      }
      default:
        rc = fsl_cx_err_set(f, FSL_RC_SYNTAX,
                            "Unknown card '%c' in manifest",
                            cType);
        goto bailout;
    }/*switch(cType)*/
    if(rc) goto bailout;
  }/* for-each-card */

#if 1
  /* Remove these when we are done porting
     resp. we can avoid these unused-var warnings. */
  if(isRepeat){}
#endif

  end:
  assert(0==rc);
  if(cardCount>2 && FSL_SATYPE_ANY==d->type){
    /* See if we need to guess the type now.
       We need(?) at least two card to ensure that this is
       free of ambiguities. */
    d->type = fsl_deck_guess_type(lettersSeen);
    if(FSL_SATYPE_ANY!=d->type){
      assert(FSL_SATYPE_INVALID!=d->type);
#if 0
      MARKER(("Guessed manifest type with %d cards: %s\n",
                cardCount, fsl_satype_cstr(d->type)));
#endif
    }
  }
  /* Make sure all of the cards we put in it belong to that deck
     type. */
  if( !fsl_deck_check_type(d, cType) ){
    rc = d->f->error.code;
    goto bailout;
  }

  if(FSL_SATYPE_ANY==d->type){
    rc = fsl_cx_err_set(f, FSL_RC_ERROR,
                        "Internal error: could not determine type of "
                        "control artifact we just (successfully!) "
                        "parsed.");
    goto bailout;
  }else {
    /*
      Make sure we didn't pick up any cards which were picked up
      before d->type was guessed and are invalid for the post-guessed
      type.
    */
    int i = 0;
    for( ; i < 27; ++i ){
      if((lettersSeen[i]>0) && !fsl_card_is_legal(d->type, 'A'+i )){
        rc = fsl_cx_err_set(f, FSL_RC_SYNTAX,
                            "Determined during post-parse processing that "
                            "the parsed deck (type %s) contains an illegal "
                            "card type (%c).", fsl_satype_cstr(d->type),
                            'A'+i);
        goto bailout;
      }
    }
  }
  assert(FSL_SATYPE_CHECKIN==d->type ||
         FSL_SATYPE_CLUSTER==d->type ||
         FSL_SATYPE_CONTROL==d->type ||
         FSL_SATYPE_WIKI==d->type ||
         FSL_SATYPE_TICKET==d->type ||
         FSL_SATYPE_ATTACHMENT==d->type ||
         FSL_SATYPE_TECHNOTE==d->type ||
         FSL_SATYPE_FORUMPOST==d->type);
  assert(0==rc);

  /* Additional checks based on artifact type */
  switch( d->type ){
    case FSL_SATYPE_CONTROL: {
      if( nSelfTag ){
        SYNTAX("self-referential T-card in control artifact");
      }
      break;
    }
    case FSL_SATYPE_TECHNOTE: {
      if( d->T.used!=nSelfTag ){
        SYNTAX("non-self-referential T-card in technote");
      }else if( d->T.used!=nSimpleTag ){
        SYNTAX("T-card with '*' or '-' in technote");
      }
      break;
    }
    case FSL_SATYPE_FORUMPOST: {
      if( d->H && d->I ){
        SYNTAX("cannot have I-card and H-card in a forum post");
      }else if( d->P.used>1 ){
        SYNTAX("too many arguments to P-card");
      }
      break;
    }
    default: break;
  }

  assert(!d->content.mem);
  if(stealBuf>0){
    /* We stashed something which points to src->mem, so we need to
       steal that memory.
    */
    d->content = *src;
    *src = fsl_buffer_empty;
  }
  d->F.flags &= ~FSL_CARD_F_LIST_NEEDS_SORT/*we know all cards were read in order*/;
  return 0;

  bailout:
  if(stealBuf>0){
    d->content = *src;
    *src = fsl_buffer_empty;
  }
  assert(0 != rc);
  if(zMsg){
    fsl_error_set(err, rc, "%s", zMsg);
  }
  return rc;
#undef SEEN
#undef TOKEN_DATETIME
#undef SYNTAX
#undef TOKEN_CHECKHEX
#undef TOKEN_EXISTS
#undef TOKEN_UUID
#undef TOKEN_MD5
#undef TOKEN
#undef ERROR
}

int fsl_deck_parse(fsl_deck * d, fsl_buffer * src){
  return fsl_deck_parse2(d, src, 0);
}

int fsl_deck_load_rid( fsl_cx * f, fsl_deck * d,
                       fsl_id_t rid, fsl_satype_e type ){
  fsl_buffer buf = fsl_buffer_empty;
  int rc = 0;
  if(!f || !d) return FSL_RC_SYNTAX;
  if(0==rid) rid = f->ckout.rid;
  if(rid<0){
    return fsl_cx_err_set(f, FSL_RC_RANGE,
                          "Invalid RID for fsl_deck_load_rid(): "
                          "%"FSL_ID_T_PFMT, rid);
  }
  fsl_deck_clean(d);
  if(fsl_cx_mcache_search(f, rid, d)){
    assert(d->f);
    if(type!=FSL_SATYPE_ANY && type!=d->type){
      rc = fsl_cx_err_set(f, FSL_RC_ERROR,
                          "Unexpected match of RID #%" FSL_ID_T_PFMT " "
                          "to a different artifact type (%d) "
                          "than requested (%d).",
                          d->type, type);
      fsl_cx_mcache_insert(f, d);
      assert(!d->f);
    }else{
      //MARKER(("Got cached deck: %s\n", d->uuid));
    }
    return rc;
  }  
  rc = fsl_content_get(f, rid, &buf);
  if(rc) goto end;
#if 0
  MARKER(("fsl_content_get(%d) len=%d =\n%.*s\n",
          (int)rid, (int)buf.used, (int)buf.used, (char const*)buf.mem));
#endif
  fsl_deck_init(f, d, FSL_SATYPE_ANY);
#if 0
  /*
    If we set d->type=type, the parser can fail more
    quickly. However, that failure will bypass our more specific
    reporting of the problem (see below).  As the type mismatch case
    is expected to be fairly rare, we'll leave this out for now, but
    it might be worth considering as a small optimization later on.
  */
  d->type = type /* may help parsing fail more quickly if
                    it's not the type we want.*/;
#endif
  rc = fsl_deck_parse(d, &buf);
  if(!rc){
    assert(rid == d->rid);
    if( type!=FSL_SATYPE_ANY && d->type!=type ){
      rc = fsl_cx_err_set(f, FSL_RC_TYPE,
                          "RID %"FSL_ID_T_PFMT" is of type %s, "
                          "but the caller requested type %s.",
                          rid,
                          fsl_satype_cstr(d->type),
                          fsl_satype_cstr(type));
    }
  }
  if( !rc && d->B.uuid ){
    rc = fsl_cx_update_seen_delta_mf(f);
  }
  end:
  fsl_buffer_clear(&buf);
  return rc;
}

int fsl_deck_load_sym( fsl_cx * f, fsl_deck * d,
                       char const * symbolicName, fsl_satype_e type ){
  if(!symbolicName || !d) return FSL_RC_MISUSE;
  else{
    fsl_id_t vid = 0;
    int rc = fsl_sym_to_rid(f, symbolicName, type, &vid);
    if(!rc){
      assert(vid>0);
      rc = fsl_deck_load_rid(f, d, vid, type);
    }
    return rc;
  }
}

  
static int fsl_deck_baseline_load( fsl_deck * d ){
  int rc = 0;
  fsl_deck bl = fsl_deck_empty;
  fsl_id_t rid;
  fsl_cx * f = d ? d->f : NULL;
  fsl_db * db = f ? fsl_needs_repo(f) : NULL;
  assert(d->f);
  assert(d);
  if(!d->f) return FSL_RC_MISUSE;
  else if(d->B.baseline || !d->B.uuid) return 0 /* nothing to do! */;
  else if(!db) return FSL_RC_NOT_A_REPO;
#if 0
  else if(d->rid<=0){
    return fsl_cx_err_set(f, FSL_RC_RANGE,
                          "fsl_deck_baseline_load(): "
                          "fsl_deck::rid is not set.");
  }
#endif
  rid = fsl_uuid_to_rid(f, d->B.uuid);
  if(rid<0){
    assert(f->error.code);
    return f->error.code;
  }
  else if(!rid){
    if(d->rid>0){
      fsl_db_exec(db, 
                  "INSERT OR IGNORE INTO orphan(rid, baseline) "
                  "VALUES(%"FSL_ID_T_PFMT",%"FSL_ID_T_PFMT")",
                  d->rid, rid);
    }
    rc = fsl_cx_err_set(f, FSL_RC_RANGE,
                        "Could not find/load baseline manifest [%s], "
                        "parent of manifest rid #%"FSL_ID_T_PFMT".",
                        d->B.uuid, d->rid);
  }else{
    rc = fsl_deck_load_rid(f, &bl, rid, FSL_SATYPE_CHECKIN);
    if(!rc){
      d->B.baseline = fsl_deck_malloc();
      if(!d->B.baseline){
        fsl_deck_clean(&bl);
        rc = FSL_RC_OOM;
      }else{
        void const * allocStampKludge = d->B.baseline->allocStamp;
        *d->B.baseline = bl /* Transfer ownership */;
        d->B.baseline->allocStamp = allocStampKludge /* But we need this intact
                                                        for deallocation to work */;
        assert(f==d->B.baseline->f);
      }
    }else{
      /* bl might be partially populated */
      fsl_deck_finalize(&bl);
    }
  }
  return rc;
}

int fsl_deck_baseline_fetch( fsl_deck * d ){
  return (d->B.baseline || !d->B.uuid)
    ? 0
    : fsl_deck_baseline_load(d);
}

int fsl_deck_F_rewind( fsl_deck * d ){
  int rc = 0;
  d->F.cursor = 0;
  assert(d->f);
  if(d->B.uuid){
    rc = fsl_deck_baseline_fetch(d);
    if(!rc){
      assert(d->B.baseline);
      d->B.baseline->F.cursor = 0;
    }
  }
  return rc;
}

int fsl_deck_F_next( fsl_deck * d, fsl_card_F const ** rv ){
  assert(d);
  assert(d->f);
  assert(rv);
#define FCARD(DECK,NDX) F_at(&(DECK)->F, NDX)
  *rv = NULL;
  if(!d->B.baseline){
    /* Manifest d is a baseline-manifest.  Just scan down the list
       of files. */
    if(d->B.uuid){
      return fsl_cx_err_set(d->f, FSL_RC_MISUSE,
                            "Deck has a B-card (%s) but no baseline "
                            "loaded. Load the baseline before calling "
                            "%s().",
                            d->B.uuid, __func__)
        /* We "could" just load the baseline from here. */;
    }
    if( d->F.cursor < (int32_t)d->F.used ){
      *rv = FCARD(d, d->F.cursor++);
      assert(*rv);
      assert((*rv)->uuid && "Baseline manifest has deleted F-card entry!");
    }
    return 0;
  }else{
    /* Manifest d is a delta-manifest.  Scan the baseline but amend the
       file list in the baseline with changes described by d.
    */
    fsl_deck * const pB = d->B.baseline;
    int cmp;
    while(1){
      if( pB->F.cursor >= (fsl_int_t)pB->F.used ){
        /* We have used all entries out of the baseline.  Return the next
           entry from the delta. */
        if( d->F.cursor < (fsl_int_t)d->F.used ) *rv = FCARD(d, d->F.cursor++);
        break;
      }else if( d->F.cursor >= (fsl_int_t)d->F.used ){
        /* We have used all entries from the delta.  Return the next
           entry from the baseline. */
        if( pB->F.cursor < (fsl_int_t)pB->F.used ) *rv = FCARD(pB, pB->F.cursor++);
        break;
      }else if( (cmp = fsl_strcmp(FCARD(pB,pB->F.cursor)->name,
                                  FCARD(d, d->F.cursor)->name)) < 0){
        /* The next baseline entry comes before the next delta entry.
           So return the baseline entry. */
        *rv = FCARD(pB, pB->F.cursor++);
        break;
      }else if( cmp>0 ){
        /* The next delta entry comes before the next baseline
           entry so return the delta entry */
        *rv = FCARD(d, d->F.cursor++);
        break;
      }else if( FCARD(d, d->F.cursor)->uuid ){
        /* The next delta entry is a replacement for the next baseline
           entry.  Skip the baseline entry and return the delta entry */
        pB->F.cursor++;
        *rv = FCARD(d, d->F.cursor++);
        break;
      }else{
        assert(0==cmp);
        /*
          The next delta entry is a delete of the next baseline entry.
        */
        /* Skip them both.  Repeat the loop to find the next
           non-delete entry. */
        pB->F.cursor++;
        d->F.cursor++;
        continue;
      }      
    }
    return 0;
  }
#undef FCARD
}

int fsl_deck_save( fsl_deck * d, bool isPrivate ){
  int rc;
  fsl_cx * f = d ? d->f : NULL;
  fsl_db * db = f ? fsl_needs_repo(f) : NULL;
  fsl_buffer buf = fsl_buffer_empty;
  fsl_id_t newRid = 0;
  bool const oldPrivate = f ? f->cache.markPrivate : 0;
  bool const isNew = d && !d->uuid;
  if(!f || !d ) return FSL_RC_MISUSE;
  else if(!db) return FSL_RC_NOT_A_REPO;
  else if( (d->rid>0 && !d->uuid) ||
           (d->rid<=0 && d->uuid)){
    return fsl_cx_err_set(f, FSL_RC_MISUSE,
                          "The input deck must have either rid _and_ UUID "
                          "or neither of rid/UUID. A mixture is ambiguous.");
  }else if(d->B.uuid && fsl_repo_forbids_delta_manifests(f)){
    return fsl_cx_err_set(f, FSL_RC_ACCESS,
                          "This deck is a delta manifest, but this "
                          "repository has disallowed those via the "
                          "forbid-delta-manifests config option.");
  }

  fsl_cx_err_reset(f);
  rc = fsl_deck_output(d, fsl_output_f_buffer, &buf);
  if(rc){
    fsl_buffer_clear(&buf);
    return rc;
  }

  rc = fsl_db_transaction_begin(db);
  if(rc){
    fsl_buffer_clear(&buf);
    return rc;
  }
  if(0){
    MARKER(("Saving deck:\n%s\n", fsl_buffer_cstr(&buf)));
  }

  /* Starting here, don't return, use (goto end) instead. */

  f->cache.markPrivate = isPrivate;
  rc = fsl_content_put_ex(f, &buf, d->uuid, 0,
                          0U, isPrivate, &newRid);
  if(rc) goto end;
  assert(newRid>0);

  rc = fsl_cx_hash_buffer( f, false, &buf, &buf );
  if(rc) goto end;

  /* We need d->uuid and d->rid for crosslinking purposes, but will
     unset them on error (if we set them) because their values will no
     longer be in the db after rollback...
  */
#if 0
  /* practice shows that this is not needed/desired. There are cases
     where round-tripping a manifest results in a 1-millisecond
     difference in the D-card (Julian Day/double), which changes the
     has but otherwise has no difference on the meaning. That said,
     the way we use decks means that we really don't care (for
     purposes of this function) what their initial UUID is except for
     the purpose of fsl_content_put_ex(), above.
  */
  if(d->uuid){
    /* Compare original and resulting hashes. */
    if(0 != fsl_uuidcmp(fsl_buffer_cstr(&buf), d->uuid)){
      rc = fsl_cx_err_set(f, FSL_RC_CONSISTENCY,
                          "Input deck's original UUID and resulting UUID "
                          "do not match: [%s] vs [%b]",
                          d->uuid, &buf);
      goto end;
    }
    /* ??? assert(d->rid==newRid); */
    d->rid = newRid;
  }else
#endif  
  {
    fsl_free(d->uuid);
    d->uuid = fsl_buffer_str(&buf) /* transfer ownership */;
    buf = fsl_buffer_empty;
    d->rid = newRid;
  }

#if 0
  /* Something to consider: if d is new and has a parent, deltify the
     parent. The branch operation does this, but it is not yet clear
     whether that is a general pattern for manifests.
  */
  if(isNew && d->P.used){
    fsl_id_t pid;
    assert(FSL_SATYPE_CHECKIN == d->type);
    pid = fsl_uuid_to_rid(f, (char const *)d->P.list[0]);
    if(pid>0){
      rc = fsl_content_deltify(f, pid, d->rid, 0);
      if(rc) goto end;
    }
  }
#endif

  if(isNew && (FSL_SATYPE_WIKI==d->type)){
    /* Analog to fossil's wiki.c:wiki_put(): */
    /*
      MISSING:
      fossil's wiki.c:wiki_put() handles the moderation bits.
    */
    if(d->P.used){
      fsl_id_t const pid = fsl_deck_P_get_id(d, 0);
      assert(pid>0);
      if(pid<0){
        assert(f->error.code);
        rc = f->error.code;
        goto end;
      }else if(!pid){
        if(!f->error.code){
          rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                              "Did not find matching RID "
                              "for P-card[0] (%s).",
                              (char const *)d->P.list[0]);
        }
        goto end;
      }

      rc = fsl_content_deltify(f, pid, d->rid, 0);
      if(rc) goto end;
    }
    rc = fsl_db_exec_multi(db,
                           "INSERT OR IGNORE INTO unsent "
                           "VALUES(%"FSL_ID_T_PFMT");"
                           "INSERT OR IGNORE INTO unclustered "
                           "VALUES(%"FSL_ID_T_PFMT");",
                           d->rid, d->rid);
    if(rc){
      fsl_cx_uplift_db_error(f, db);
      goto end;
    }
  }

  rc = f->cache.isCrosslinking
    ? fsl_deck_crosslink(d)
    : fsl_deck_crosslink_one(d);

  end:
  f->cache.markPrivate = oldPrivate;
  if(!rc) rc = fsl_db_transaction_end( db, 0);
  else fsl_db_transaction_end(db, 1);
  if(rc){
    if(isNew){
      fsl_free(d->uuid);
      d->uuid = NULL;
      d->rid = 0;
    }
    if(!f->error.code && db->error.code){
      rc = fsl_cx_uplift_db_error(f, db);
    }
  }
  fsl_buffer_clear(&buf);
  return rc;
}

int fsl_crosslink_end(fsl_cx * f){
  int rc = 0;
  fsl_db * db = fsl_cx_db_repo(f);
  fsl_stmt q = fsl_stmt_empty;
  fsl_stmt u = fsl_stmt_empty;
  int i;
  assert(f);
  assert(db);
  assert(f->cache.isCrosslinking);
  if(!f->cache.isCrosslinking){
    return fsl_cx_err_set(f, FSL_RC_MISUSE,
                          "Crosslink is not running.");
  }
  f->cache.isCrosslinking = false;
  assert(db->beginCount > 0);

  /* Handle any reparenting via tags... */
  rc = fsl_db_prepare(db, &q,
                     "SELECT rid, value FROM tagxref"
                      " WHERE tagid=%d AND tagtype=%d",
                      (int)FSL_TAGID_PARENT, (int)FSL_TAGTYPE_ADD);
  if(rc) goto end;
  while(FSL_RC_STEP_ROW==fsl_stmt_step(&q)){
    fsl_id_t const rid = fsl_stmt_g_id(&q, 0);
    const char *zTagVal = fsl_stmt_g_text(&q, 1, 0);
    rc = fsl_crosslink_reparent(f,rid, zTagVal);
    if(rc) break;
  }
  fsl_stmt_finalize(&q);
  if(rc) goto end;

  /* Process entries from pending_xlink temp table... */
  rc = fsl_db_prepare(db, &q, "SELECT id FROM pending_xlink");
  if(rc) goto end;
  while( FSL_RC_STEP_ROW==fsl_stmt_step(&q) ){
    const char *zId = fsl_stmt_g_text(&q, 0, NULL);
    char cType;
    if(!zId || !*zId) continue;
    cType = zId[0];
    ++zId;
    if('t'==cType){
      /* FSL-MISSING:
         ticket_rebuild_entry(zId) */
      continue;
    }else if('w'==cType){
      /* FSL-MISSING:
         backlink_wiki_refresh(zId) */
      continue;
    }
  }
  fsl_stmt_finalize(&q);
  rc = fsl_db_exec(db, "DROP TABLE pending_xlink");
  if(rc) goto end;
  /* If multiple check-ins happen close together in time, adjust their
     times by a few milliseconds to make sure they appear in chronological
     order.
  */
  rc = fsl_db_prepare(db, &q,
                      "UPDATE time_fudge SET m1=m2-:incr "
                      "WHERE m1>=m2 AND m1<m2+:window"
  );
  if(rc) goto end;
  fsl_stmt_bind_double_name(&q, ":incr", AGE_ADJUST_INCREMENT);
  fsl_stmt_bind_double_name(&q, ":window", AGE_FUDGE_WINDOW);
  rc = fsl_db_prepare(db, &u,
                      "UPDATE time_fudge SET m2="
                      "(SELECT x.m1 FROM time_fudge AS x"
                      " WHERE x.mid=time_fudge.cid)");
  for(i=0; !rc && i<30; i++){ /* where does 30 come from? */
    rc = fsl_stmt_step(&q);
    if(FSL_RC_STEP_DONE==rc) rc=0;
    else break;
    fsl_stmt_reset(&q);
    if( fsl_db_changes_recent(db)==0 ) break;
    rc = fsl_stmt_step(&u);
    if(FSL_RC_STEP_DONE==rc) rc=0;
    else break;
    fsl_stmt_reset(&u);
  }
  fsl_stmt_finalize(&q);
  fsl_stmt_finalize(&u);
  if(!rc && fsl_db_exists(db,"SELECT 1 FROM time_fudge")){
    rc = fsl_db_exec(db, "UPDATE event SET"
                     " mtime=(SELECT m1 FROM time_fudge WHERE mid=objid)"
                     " WHERE objid IN (SELECT mid FROM time_fudge)"
                     " AND (mtime=omtime OR omtime IS NULL)"
                     );
  }
  end:
  rc = fsl_cx_uplift_db_error2(f, db, rc)
    /* Do before drop time_fudge to ensure we don't
       clear the error state by accident. */;
  if(!rc){
    fsl_db_exec(db, "DROP TABLE time_fudge");
  }
  if(rc) fsl_db_transaction_rollback(db);
  else rc = fsl_db_transaction_commit(db);
  return fsl_cx_uplift_db_error2(f, db, rc);
}

int fsl_crosslink_begin(fsl_cx * f){
  int rc;
  fsl_db * db = fsl_cx_db_repo(f);
  assert(f);
  assert(db);
  assert(0==f->cache.isCrosslinking);
  if(f->cache.isCrosslinking){
    return fsl_cx_err_set(f, FSL_RC_MISUSE,
                          "Crosslink is already running.");
  }
  rc = fsl_db_transaction_begin(db);
  if(rc) return fsl_cx_uplift_db_error(f, db);
  rc = fsl_db_exec_multi(db,
     "CREATE TEMP TABLE pending_xlink(id TEXT PRIMARY KEY)WITHOUT ROWID;"
     "CREATE TEMP TABLE time_fudge("
     "  mid INTEGER PRIMARY KEY,"    /* The rid of a manifest */
     "  m1 REAL,"                    /* The timestamp on mid */
     "  cid INTEGER,"                /* A child or mid */
     "  m2 REAL"                     /* Timestamp on the child */
     ");");
  if(!rc){
    f->cache.isCrosslinking = 1;
    return 0;
  }else{
    rc = fsl_cx_uplift_db_error2(f, db, rc);
    fsl_db_transaction_rollback(db);
    return rc;
  }
}

#undef MARKER
#undef AGE_FUDGE_WINDOW
#undef AGE_ADJUST_INCREMENT
#undef F_at
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/delta.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 houses Fossil's delta generation and application
  routines. This code is functionally independent of the rest of the
  library, relying only on fsl_malloc(), fsl_free(), and the integer
  typedefs defined by the configuration process. i.e. it can easily
  be pulled out and used in arbitrary projects.
*/
#include "fossil-scm/fossil.h"
#include <memory.h>
#include <stdlib.h>

/**
   2021-03-10: The delta checksum self-test is a significant run-time
   sink when processing many deltas. Fossil does not enable this
   feature by default so we'll leave it off by default, too.
*/
#if !defined(FSL_OMIT_DELTA_CKSUM_TEST)
#  define FSL_OMIT_DELTA_CKSUM_TEST
#endif

/*
   Macros for turning debugging printfs on and off
*/
#if 0
# define DEBUG1(X) X
#else
# define DEBUG1(X)
#endif
#if 0
#define DEBUG2(X) X
/*
   For debugging:
   Print 16 characters of text from zBuf
*/
static const char *print16(const char *z){
  int i;
  static char zBuf[20];
  for(i=0; i<16; i++){
    if( z[i]>=0x20 && z[i]<=0x7e ){
      zBuf[i] = z[i];
    }else{
      zBuf[i] = '.';
    }
  }
  zBuf[i] = 0;
  return zBuf;
}
#else
# define DEBUG2(X)
#endif

/*
   The width of a hash window in bytes.  The algorithm only works if this
   is a power of 2.
*/
#define NHASH 16

/*
   The current state of the rolling hash.
  
   z[] holds the values that have been hashed.  z[] is a circular buffer.
   z[i] is the first entry and z[(i+NHASH-1)%NHASH] is the last entry of 
   the window.
  
   Hash.a is the sum of all elements of hash.z[].  Hash.b is a weighted
   sum.  Hash.b is z[i]*NHASH + z[i+1]*(NHASH-1) + ... + z[i+NHASH-1]*1.
   (Each index for z[] should be module NHASH, of course.  The %NHASH operator
   is omitted in the prior expression for brevity.)
*/
typedef struct fsl_delta_hash fsl_delta_hash;
struct fsl_delta_hash {
  uint16_t a, b;         /* Hash values */
  uint16_t i;            /* Start of the hash window */
  unsigned char z[NHASH];    /* The values that have been hashed */
};

/*
   Initialize the rolling hash using the first NHASH characters of z[]
*/
static void fsl_delta_hash_init(fsl_delta_hash *pHash,
                                unsigned char const *z){
  uint16_t a, b, i;
  a = b = 0;
  for(i=0; i<NHASH; i++){
    a += z[i];
    b += a;
  }
  memcpy(pHash->z, z, NHASH);
  pHash->a = a & 0xffff;
  pHash->b = b & 0xffff;
  pHash->i = 0;
}

/*
   Advance the rolling hash by a single character "c"
*/
static void fsl_delta_hash_next(fsl_delta_hash *pHash, int c){
  uint16_t old = pHash->z[pHash->i];
  pHash->z[pHash->i] = c;
  pHash->i = (pHash->i+1)&(NHASH-1);
  pHash->a = pHash->a - old + c;
  pHash->b = pHash->b - NHASH*old + pHash->a;
}

/*
   Return a 32-bit hash value
*/
static uint32_t fsl_delta_hash_32bit(fsl_delta_hash *pHash){
  return (pHash->a & 0xffff) | (((uint32_t)(pHash->b & 0xffff))<<16);
}

/**
   Compute a hash on NHASH bytes.

   This routine is intended to be equivalent to:
   fsl_delta_hash h;
   fsl_delta_hash_init(&h, zInput);
   return fsl_delta_hash_32bit(&h);
*/
static uint32_t fsl_delta_hash_once(unsigned const char *z){
  uint16_t a = 0, b = 0, i = 0;
  for(i=0; i<NHASH; ++i){
    a += z[i];
    b += a;
  }
  return a | (((uint32_t)b)<<16);
}

/*
   Write an base-64 integer into the given buffer. Return its length.
*/
static unsigned int fsl_delta_int_put(uint32_t v, unsigned char **pz){
  static const char zDigits[] = 
    "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~";
  /*  123456789 123456789 123456789 123456789 123456789 123456789 123 */
  int i, j;
  unsigned char zBuf[20];
  fsl_size_t rc = 0;
  if( v==0 ){
    *(*pz)++ = '0';
    rc = 1;
  }else{
    for(i=0; v>0; ++rc, ++i, v>>=6){
      zBuf[i] = zDigits[v&0x3f];
    }
    zBuf[i]=0;
    for(j=i-1; j>=0; j--){
      *(*pz)++ = zBuf[j];
    }
  }
  return rc;
}

/*
   Read bytes from *pz and convert them into a positive integer.  When
   finished, leave *pz pointing to the first character past the end of
   the integer.  The *pLen parameter holds the length of the string
   in *pz and is decremented once for each character in the integer.
*/
static fsl_size_t fsl_delta_int_get(unsigned char const **pz, fsl_int_t *pLen){
  static const signed char zValue[] = {
    -1, -1, -1, -1, -1, -1, -1, -1,   -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1,   -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1,   -1, -1, -1, -1, -1, -1, -1, -1,
     0,  1,  2,  3,  4,  5,  6,  7,    8,  9, -1, -1, -1, -1, -1, -1,
    -1, 10, 11, 12, 13, 14, 15, 16,   17, 18, 19, 20, 21, 22, 23, 24,
    25, 26, 27, 28, 29, 30, 31, 32,   33, 34, 35, -1, -1, -1, -1, 36,
    -1, 37, 38, 39, 40, 41, 42, 43,   44, 45, 46, 47, 48, 49, 50, 51,
    52, 53, 54, 55, 56, 57, 58, 59,   60, 61, 62, -1, -1, -1, 63, -1,
  };
  fsl_size_t v = 0;
  fsl_int_t c;
  unsigned char const *z = (unsigned char const*)*pz;
  unsigned char const *zStart = z;
  while( (c = zValue[0x7f&*(z++)])>=0 ){
     v = (v<<6) + c;
  }
  z--;
  *pLen -= z - zStart;
  *pz = z;
  return v;
}

/*
   Return the number digits in the base-64 representation of a positive integer
*/
static int fsl_delta_digit_count(fsl_int_t v){
  unsigned int x;
  int i;
  for(i=1, x=64; v>=(fsl_int_t)x; i++, x <<= 6){}
  return i;
}

/*
   Compute a 32-bit checksum on the N-byte buffer.  Return the result.
*/
static unsigned int fsl_delta_checksum(void const *zIn, fsl_size_t N){
  const unsigned char *z = (const unsigned char *)zIn;
  unsigned sum0 = 0;
  unsigned sum1 = 0;
  unsigned sum2 = 0;
  unsigned sum3 = 0;
  while(N >= 16){
    sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]);
    sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]);
    sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]);
    sum3 += ((unsigned)z[3] + z[7] + z[11]+ z[15]);
    z += 16;
    N -= 16;
  }
  while(N >= 4){
    sum0 += z[0];
    sum1 += z[1];
    sum2 += z[2];
    sum3 += z[3];
    z += 4;
    N -= 4;
  }
  sum3 += (sum2 << 8) + (sum1 << 16) + (sum0 << 24);
  switch(N){
    case 3:   sum3 += (z[2] << 8);
    case 2:   sum3 += (z[1] << 16);
    case 1:   sum3 += (z[0] << 24);
    default:  ;
  }
  return sum3;
}

int fsl_delta_create2( unsigned char const *zSrc, fsl_size_t lenSrc,
                       unsigned char const *zOut, fsl_size_t lenOut,
                       fsl_output_f out, void * outState){
  enum { IntegerBufSize = 50 /* buffer size for integer conversions. */};
  unsigned int i, base;
  unsigned int nHash;          /* Number of hash table entries */
  unsigned int *landmark;      /* Primary hash table */
  unsigned int *collide = NULL;  /* Collision chain */
  int lastRead = -1;           /* Last byte of zSrc read by a COPY command */
  int rc;                      /* generic return code checker. */
  unsigned int olen = 0;       /* current output length. */
  unsigned int total = 0;      /* total byte count. */
  fsl_delta_hash h;
  unsigned char theBuf[IntegerBufSize] = {0,};
  unsigned char * intBuf = theBuf;
  if(!zSrc || !zOut || !out) return FSL_RC_MISUSE;
  /* Add the target file size to the beginning of the delta
  */
#ifdef OUT
#undef OUT
#endif
#define OUT(BLOB,LEN) rc=out(outState, BLOB, LEN); if(0 != rc) {fsl_free(collide); return rc;} else total += LEN
#define OUTCH(CHAR) OUT(CHAR,1)
#define PINT(I) intBuf = theBuf; olen=fsl_delta_int_put(I, &intBuf); OUT(theBuf,olen)
  PINT(lenOut);
  OUTCH("\n");

  /* If the source file is very small, it means that we have no
     chance of ever doing a copy command.  Just output a single
     literal segment for the entire target and exit.
  */
  if( lenSrc<=NHASH ){
    PINT(lenOut);
    OUTCH(":");
    OUT(zOut,lenOut);
    PINT((fsl_delta_checksum(zOut, lenOut)));
    OUTCH(";");
    return 0;
  }

  /* Compute the hash table used to locate matching sections in the
     source file.
  */
  nHash = lenSrc/NHASH;
  collide = (unsigned int *)malloc( nHash*2*sizeof(int) );
  if(!collide){
    return FSL_RC_OOM;
  }
  landmark = &collide[nHash];
  memset(landmark, -1, nHash*sizeof(int));
  memset(collide, -1, nHash*sizeof(int));
  for(i=0; i<lenSrc-NHASH; i+=NHASH){
    uint32_t const hv = fsl_delta_hash_once(&zSrc[i]) % nHash;
    collide[i/NHASH] = landmark[hv];
    landmark[hv] = i/NHASH;
  }

  /* Begin scanning the target file and generating copy commands and
     literal sections of the delta.
  */
  base = 0;    /* We have already generated everything before zOut[base] */
  while( base+NHASH<lenOut ){
    fsl_int_t iSrc;
    int iBlock
      /* WEIRD: if i change this from int to fsl_int_t
         we end up in an infinite loop somewhere. int
         and short both work*/;
    fsl_int_t bestCnt, bestOfst=0, bestLitsz=0;
    fsl_delta_hash_init(&h, &zOut[base]);
    i = 0;     /* Trying to match a landmark against zOut[base+i] */
    bestCnt = 0;
    while( 1 ){
      uint32_t hv;
      int limit = 250;

      hv = fsl_delta_hash_32bit(&h) % nHash;
      DEBUG2( printf("LOOKING: %4d [%s]\n", base+i, print16(&zOut[base+i])); )
      iBlock = (int)landmark[hv];
      while( iBlock>=0 && (limit--)>0 ){
        /*
           The hash window has identified a potential match against 
           landmark block iBlock.  But we need to investigate further.
           
           Look for a region in zOut that matches zSrc. Anchor the search
           at zSrc[iSrc] and zOut[base+i].  Do not include anything prior to
           zOut[base] or after zOut[outLen] nor anything after zSrc[srcLen].
          
           Set cnt equal to the length of the match and set ofst so that
           zSrc[ofst] is the first element of the match.  litsz is the number
           of characters between zOut[base] and the beginning of the match.
           sz will be the overhead (in bytes) needed to encode the copy
           command.  Only generate copy command if the overhead of the
           copy command is less than the amount of literal text to be copied.
        */
        fsl_int_t cnt, ofst, litsz;
        fsl_int_t j, k, x, y;
        fsl_int_t sz;
        fsl_int_t limitX;

        /* Beginning at iSrc, match forwards as far as we can.  j counts
           the number of characters that match */
        iSrc = iBlock*NHASH;
        y = base + i;
        limitX = ( lenSrc-iSrc <= lenOut-y ) ? lenSrc : iSrc + lenOut - y;
        for(x=iSrc; x<limitX; ++x, ++y){
          if( zSrc[x]!=zOut[y] ) break;
        }
        j = x - iSrc - 1;

        /* Beginning at iSrc-1, match backwards as far as we can.  k counts
           the number of characters that match */
        for(k=1; k<iSrc && k<=i; ++k){
          if( zSrc[iSrc-k]!=zOut[base+i-k] ) break;
        }
        --k;

        /* Compute the offset and size of the matching region */
        ofst = iSrc-k;
        cnt = j+k+1;
        litsz = i-k;  /* Number of bytes of literal text before the copy */
        DEBUG2( printf("MATCH %d bytes at %d: [%s] litsz=%d\n",
                        cnt, ofst, print16(&zSrc[ofst]), litsz); )
        /* sz will hold the number of bytes needed to encode the "insert"
           command and the copy command, not counting the "insert" text */
        sz = fsl_delta_digit_count(i-k)
          +fsl_delta_digit_count(cnt)
          +fsl_delta_digit_count(ofst)
          +3;
        if( cnt>=sz && cnt>bestCnt ){
          /* Remember this match only if it is the best so far and it
             does not increase the file size */
          bestCnt = cnt;
          bestOfst = iSrc-k;
          bestLitsz = litsz;
          DEBUG2( printf("... BEST SO FAR\n"); )
        }

        /* Check the next matching block */
        iBlock = collide[iBlock];
      }

      /* We have a copy command that does not cause the delta to be larger
         than a literal insert.  So add the copy command to the delta.
      */
      if( bestCnt>0 ){
        if( bestLitsz>0 ){
          /* Add an insert command before the copy */
          PINT(bestLitsz);
          OUTCH(":");
          OUT(zOut+base, bestLitsz);
          base += bestLitsz;
          DEBUG2( printf("insert %d\n", bestLitsz); )
        }
        base += bestCnt;
        PINT(bestCnt);
        OUTCH("@");
        PINT(bestOfst);
        DEBUG2( printf("copy %d bytes from %d\n", bestCnt, bestOfst); )
        OUTCH(",");
        if( bestOfst + bestCnt -1 > lastRead ){
          lastRead = bestOfst + bestCnt - 1;
          DEBUG2( printf("lastRead becomes %d\n", lastRead); )
        }
        bestCnt = 0;
        break;
      }

      /* If we reach this point, it means no match is found so far */
      if( base+i+NHASH>=lenOut ){
        /* We have reached the end of the input and have not found any
           matches.  Do an "insert" for everything that does not match */
        PINT(lenOut-base);
        OUTCH(":");
        OUT(zOut+base, lenOut-base);
        base = lenOut;
        break;
      }

      /* Advance the hash by one character.  Keep looking for a match */
      fsl_delta_hash_next(&h, zOut[base+i+NHASH]);
      i++;
    }
  }
  fsl_free(collide);
  /* Output a final "insert" record to get all the text at the end of
     the file that does not match anything in the source file.
  */
  if( base<lenOut ){
    PINT(lenOut-base);
    OUTCH(":");
    OUT(zOut+base, lenOut-base);
  }
  /* Output the final checksum record. */
  PINT(fsl_delta_checksum(zOut, lenOut));
  OUTCH(";");
  return 0;
#undef PINT
#undef OUT
#undef OUTCH
}

struct DeltaOutputString {
  unsigned char * mem;
  unsigned int cursor;
};
typedef struct DeltaOutputString DeltaOutputString;

/** fsl_output_f() impl which requires state to be a
    (DeltaOutputString*). Copies the first n bytes of src to
    state->mem, increments state->cursor by n, and returns 0.
 */
static int fsl_output_f_ostring( void * state, void const * src,
                                 fsl_size_t n ){
  DeltaOutputString * os = (DeltaOutputString*)state;
  memcpy( os->mem + os->cursor, src, n );
  os->cursor += n;
  return 0;
}

int fsl_delta_create( unsigned char const *zSrc, fsl_size_t lenSrc,
                      unsigned char const *zOut, fsl_size_t lenOut,
                      unsigned char *zDelta, fsl_size_t * deltaSize){
  int rc;
  DeltaOutputString os;
  if(!zSrc || !zOut || !zDelta || !deltaSize) return FSL_RC_MISUSE;
  os.mem = (unsigned char *)zDelta;
  os.cursor = 0;
  rc = fsl_delta_create2( zSrc, lenSrc, zOut, lenOut,
                          fsl_output_f_ostring, &os );
  if(!rc){
    os.mem[os.cursor] = 0;
    if(deltaSize) *deltaSize = os.cursor;
  }
  return rc;
}

/*
   Calculates the size (in bytes) of the output from applying a
   delta. On success 0 is returned and *deltaSize will be updated with
   the amount of memory required for applying the delta.
  
   This routine is provided so that an procedure that is able
   to call fsl_delta_apply() can learn how much space is required
   for the output and hence allocate nor more space that is really
   needed.
*/
int fsl_delta_applied_size(unsigned char const *zDelta, fsl_size_t lenDelta_,
                           fsl_size_t * deltaSize){
  if(!zDelta || (lenDelta_<2) || !deltaSize) return FSL_RC_MISUSE;
  else{
    fsl_size_t size;
    fsl_int_t lenDelta = (fsl_int_t)lenDelta_;
    size = fsl_delta_int_get(&zDelta, &lenDelta);
    if( *zDelta!='\n' ){
      /* ERROR: size integer not terminated by "\n" */
      return FSL_RC_DELTA_INVALID_TERMINATOR;
    }
    *deltaSize = size;
    return 0;
  }
}


int fsl_delta_apply2(
  unsigned char const *zSrc,      /* The source or pattern file */
  fsl_size_t lenSrc_,            /* Length of the source file */
  unsigned char const *zDelta,    /* Delta to apply to the pattern */
  fsl_size_t lenDelta_,          /* Length of the delta */
  unsigned char *zOut,             /* Write the output into this preallocated buffer */
  fsl_error * pErr
){
  fsl_size_t limit;
  fsl_size_t total = 0;
#if !defined(FSL_OMIT_DELTA_CKSUM_TEST)
  unsigned char *zOrigOut = zOut;
#endif
  /* lenSrc/lenDelta are cast to ints to avoid any potential side-effects
     caused by changing the function signature from signed to unsigned
     int types when porting from v1.
  */
  fsl_int_t lenSrc = (fsl_int_t)lenSrc_;
  fsl_int_t lenDelta = (fsl_int_t)lenDelta_;
  if(!zSrc || !zDelta || !zOut) return FSL_RC_MISUSE;
  else if(lenSrc<0 || lenDelta<0) return FSL_RC_RANGE;
  limit = fsl_delta_int_get(&zDelta, &lenDelta);
  if( *zDelta!='\n' ){
    if(pErr){
      fsl_error_set(pErr,
                    FSL_RC_DELTA_INVALID_TERMINATOR,
                    "Delta: size integer not terminated by \\n");
    }
    return FSL_RC_DELTA_INVALID_TERMINATOR;
  }
  zDelta++; lenDelta--;
  while( *zDelta && lenDelta>0 ){
    fsl_int_t cnt, ofst;
    cnt = fsl_delta_int_get(&zDelta, &lenDelta);
    switch( zDelta[0] ){
      case '@': {
        zDelta++; lenDelta--;
        ofst = fsl_delta_int_get(&zDelta, &lenDelta);
        if( lenDelta>0 && zDelta[0]!=',' ){
          /* ERROR: copy command not terminated by ',' */
          if(pErr){
            fsl_error_set(pErr,
                          FSL_RC_DELTA_INVALID_TERMINATOR,
                          "Delta: copy command not terminated by ','");
          }
          return FSL_RC_DELTA_INVALID_TERMINATOR;
        }
        zDelta++; lenDelta--;
        DEBUG1( printf("COPY %d from %d\n", cnt, ofst); )
        total += cnt;
        if( total>limit ){
          if(pErr){
            fsl_error_set(pErr, FSL_RC_RANGE,
                          "Delta: copy exceeds output file size");
          }
          return FSL_RC_RANGE;
        }
        if( ofst+cnt > lenSrc ){
          if(pErr){
            fsl_error_set(pErr, FSL_RC_RANGE,
                          "Delta: copy extends past end of input");
          }
          return FSL_RC_RANGE;
        }
        memcpy(zOut, &zSrc[ofst], cnt);
        zOut += cnt;
        break;
      }
      case ':': {
        zDelta++; lenDelta--;
        total += cnt;
        if( total>limit ){
          if(pErr){
            fsl_error_set(pErr, FSL_RC_RANGE,
                          "Delta: insert command gives an output "
                          "larger than predicted");
          }
          return FSL_RC_RANGE;
        }
        DEBUG1( printf("INSERT %d\n", cnt); )
        if( cnt>lenDelta ){
          if(pErr){
            fsl_error_set(pErr, FSL_RC_RANGE,
                          "Delta: insert count exceeds size of delta");
          }
          return FSL_RC_RANGE;
        }
        memcpy(zOut, zDelta, cnt);
        zOut += cnt;
        zDelta += cnt;
        lenDelta -= cnt;
        break;
      }
      case ';': {
        zDelta++; lenDelta--;
        zOut[0] = 0;
#if !defined(FSL_OMIT_DELTA_CKSUM_TEST)
        if( cnt!=fsl_delta_checksum(zOrigOut, total) ){
          if(pErr){
            fsl_error_set(pErr, FSL_RC_CHECKSUM_MISMATCH,
                          "Delta: bad checksum");
          }
          return FSL_RC_CHECKSUM_MISMATCH;
        }
#endif
        if( total!=limit ){
          if(pErr){
            fsl_error_set(pErr, FSL_RC_SIZE_MISMATCH,
                          "Delta: generated size does not match "
                          "predicted size");
          }
          return FSL_RC_SIZE_MISMATCH;
        }
        return 0;
      }
      default: {
        if(pErr){
          fsl_error_set(pErr, FSL_RC_DELTA_INVALID_OPERATOR,
                        "Delta: unknown delta operator");
        }
        return FSL_RC_DELTA_INVALID_OPERATOR;
      }
    }
  }
  /* ERROR: unterminated delta */
  if(pErr){
    fsl_error_set(pErr, FSL_RC_DELTA_INVALID_TERMINATOR,
                  "Delta: unterminated delta");
  }
  return FSL_RC_DELTA_INVALID_TERMINATOR;
}
int fsl_delta_apply(
  unsigned char const *zSrc,      /* The source or pattern file */
  fsl_size_t lenSrc_,            /* Length of the source file */
  unsigned char const *zDelta,    /* Delta to apply to the pattern */
  fsl_size_t lenDelta_,          /* Length of the delta */
  unsigned char *zOut             /* Write the output into this preallocated buffer */
){
  return fsl_delta_apply2(zSrc, lenSrc_, zDelta, lenDelta_, zOut, NULL);
}

#undef NHASH
#undef DEBUG1
#undef DEBUG2
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<














































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/diff.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 houses Fossil's diff-generation routines (as opposed to
  the delta-generation).
*/
#include "fossil-scm/fossil.h"
#include <assert.h>
#include <memory.h>
#include <stdlib.h>
#include <string.h> /* for memmove()/strlen() */

typedef uint64_t u64;
typedef void ReCompiled /* porting crutch. i would strongly prefer to
                           replace the regex support with a stateful
                           predicate callback.
                        */;
#define TO_BE_STATIC /* Porting crutch for unused static funcs */

#define DIFF_CONTEXT_MASK ((u64)0x0000ffff) /* Lines of context. Default if 0 */
#define DIFF_WIDTH_MASK   ((u64)0x00ff0000) /* side-by-side column width */
#define DIFF_IGNORE_EOLWS ((u64)0x01000000) /* Ignore end-of-line whitespace */
#define DIFF_IGNORE_ALLWS ((u64)0x03000000) /* Ignore all whitespace */
#define DIFF_SIDEBYSIDE   ((u64)0x04000000) /* Generate a side-by-side diff */
#define DIFF_VERBOSE      ((u64)0x08000000) /* Missing shown as empty files */
#define DIFF_BRIEF        ((u64)0x10000000) /* Show filenames only */
#define DIFF_HTML         ((u64)0x20000000) /* Render for HTML */
#define DIFF_LINENO       ((u64)0x40000000) /* Show line numbers */
#define DIFF_NOOPT        (((u64)0x01)<<32) /* Suppress optimizations (debug) */
#define DIFF_INVERT       (((u64)0x02)<<32) /* Invert the diff (debug) */
#define DIFF_CONTEXT_EX   (((u64)0x04)<<32) /* Use context even if zero */
#define DIFF_NOTTOOBIG    (((u64)0x08)<<32) /* Only display if not too big */
#define DIFF_STRIP_EOLCR  (((u64)0x10)<<32) /* Strip trailing CR */

/* Annotation flags (any DIFF flag can be used as Annotation flag as well) */
#define ANN_FILE_VERS   (((u64)0x20)<<32) /* Show file vers rather than commit vers */
#define ANN_FILE_ANCEST (((u64)0x40)<<32) /* Prefer check-ins in the ANCESTOR table */

/**
    Maximum length of a line in a text file, in bytes.  (2**13 = 8192 bytes)
 */
#define LENGTH_MASK_SZ  13
#define LENGTH_MASK     ((1<<LENGTH_MASK_SZ)-1)


/*
  ANSI escape codes: https://en.wikipedia.org/wiki/ANSI_escape_code
*/
#define ANSI_COLOR_BLACK(BOLD) ((BOLD) ? "\x1b[30m" : "\x1b[30m")
#define ANSI_COLOR_RED(BOLD) ((BOLD) ? "\x1b[31;1m" : "\x1b[31m")
#define ANSI_COLOR_GREEN(BOLD) ((BOLD) ? "\x1b[32;1m" : "\x1b[32m")
#define ANSI_COLOR_YELLOW(BOLD) ((BOLD) ? "\x1b[33;1m" : "\x1b[33m")
#define ANSI_COLOR_BLUE(BOLD) ((BOLD) ? "\x1b[34;1m" : "\x1b[34m")
#define ANSI_COLOR_MAGENTA(BOLD) ((BOLD) ? "\x1b[35;1m" : "\x1b[35m")
#define ANSI_COLOR_CYAN(BOLD) ((BOLD) ? "\x1b[36;1m" : "\x1b[36m")
#define ANSI_COLOR_WHITE(BOLD) ((BOLD) ? "\x1b[37;1m" : "\x1b[37m")
#define ANSI_DIFF_ADD(BOLD) ANSI_COLOR_GREEN(BOLD)
#define ANSI_DIFF_RM(BOLD) ANSI_COLOR_RED(BOLD)
#define ANSI_DIFF_MOD(BOLD) ANSI_COLOR_BLUE(BOLD)
#define ANSI_BG_BLACK(BOLD) ((BOLD) ? "\x1b[40;1m" : "\x1b[40m")
#define ANSI_BG_RED(BOLD) ((BOLD) ? "\x1b[41;1m" : "\x1b[41m")
#define ANSI_BG_GREEN(BOLD) ((BOLD) ? "\x1b[42;1m" : "\x1b[42m")
#define ANSI_BG_YELLOW(BOLD) ((BOLD) ? "\x1b[43;1m" : "\x1b[43m")
#define ANSI_BG_BLUE(BOLD) ((BOLD) ? "\x1b[44;1m" : "\x1b[44m")
#define ANSI_BG_MAGENTA(BOLD) ((BOLD) ? "\x1b[45;1m" : "\x1b[45m")
#define ANSI_BG_CYAN(BOLD) ((BOLD) ? "\x1b[46;1m" : "\x1b[46m")
#define ANSI_BG_WHITE(BOLD) ((BOLD) ? "\x1b[47;1m" : "\x1b[47m")
#define ANSI_RESET_COLOR   "\x1b[39;49m"
#define ANSI_RESET_ALL     "\x1b[0m"
#define ANSI_RESET ANSI_RESET_ALL
/*#define ANSI_BOLD     ";1m"*/

/**
    Extract the number of lines of context from diffFlags.
 */
static int diff_context_lines(uint64_t diffFlags){
  int n = diffFlags & DIFF_CONTEXT_MASK;
  if( n==0 && (diffFlags & DIFF_CONTEXT_EX)==0 ) n = 5;
  return n;
}

/*
   Extract the width of columns for side-by-side diff.  Supply an
   appropriate default if no width is given.
*/
static int diff_width(uint64_t diffFlags){
  int w = (diffFlags & DIFF_WIDTH_MASK)/(DIFF_CONTEXT_MASK+1);
  if( w==0 ) w = 80;
  return w;
}

/**
    Converts mask of public fsl_diff_flag_t (32-bit) values to the
    Fossil-internal 64-bit bitmask used by the DIFF_xxx macros. Why?
    (A) fossil(1) uses the macro approach and a low-level encoding of
    data in the bitmask (e.g. the context lines count). The public API
    hides the lower-level flags and allows the internal API to take
    care of the encoding.
*/
static uint64_t fsl_diff_flags_convert( int mask ){
  uint64_t rc = 0U;
#define DO(F) if( (mask & F)==F ) rc |= (((u64)F) << 24)
  DO(FSL_DIFF_IGNORE_EOLWS);
  DO(FSL_DIFF_IGNORE_ALLWS);
  DO(FSL_DIFF_SIDEBYSIDE);
  DO(FSL_DIFF_VERBOSE);
  DO(FSL_DIFF_BRIEF);
  DO(FSL_DIFF_HTML);
  DO(FSL_DIFF_LINENO);
  DO(FSL_DIFF_NOOPT);
  DO(FSL_DIFF_INVERT);
  DO(FSL_DIFF_NOTTOOBIG);
  DO(FSL_DIFF_STRIP_EOLCR);
#undef DO
  return rc;
}

/*
   Information about each line of a file being diffed.
  
   The lower LENGTH_MASK_SZ bits of the hash (DLine.h) are the length
   of the line.  If any line is longer than LENGTH_MASK characters,
   the file is considered binary.
*/
typedef struct DLine DLine;
struct DLine {
  const char *z;        /* The text of the line */
  unsigned int h;       /* Hash of the line */
  unsigned short indent;  /* Indent of the line. Only !=0 with -w/-Z option */
  unsigned short n;     /* number of bytes */
  unsigned int iNext;   /* 1+(Index of next line with same the same hash) */

  /* an array of DLine elements serves two purposes.  The fields
     above are one per line of input text.  But each entry is also
     a bucket in a hash table, as follows: */
  unsigned int iHash;   /* 1+(first entry in the hash chain) */
};

/*
   Length of a dline
*/
#define LENGTH(X)   ((X)->n)

/*
   Return an array of DLine objects containing a pointer to the
   start of each line and a hash of that line.  The lower
   bits of the hash store the length of each line.
  
   Trailing whitespace is removed from each line.  2010-08-20:  Not any
   more.  If trailing whitespace is ignored, the "patch" command gets
   confused by the diff output.  Ticket [a9f7b23c2e376af5b0e5b]
  
   If the file is binary or contains a line that is too long, or is
   empty, it sets *pOut to NULL, *pnLine to 0. It returns 0 if no
   lines are found, FSL_RC_TYPE if the input is binary, and
   FSL_RC_RANGE if a some number (e.g. the number of columns) is out
   of range.
  
   Profiling shows that in most cases this routine consumes the bulk
   of the CPU time on a diff.
  
   TODO: enhance the error reporting: add a (fsl_error*) arg.
*/
static int break_into_lines(const char *z, int n, int *pnLine,
                            DLine **pOut, int diffFlags){
  int nLine, i, j, k, s, x;
  unsigned int h, h2;
  DLine *a = NULL;
  /* Count the number of lines.  Allocate space to hold
     the returned array.
  */
  for(i=j=0, nLine=1; i<n; i++, j++){
    int c = z[i];
    if( c==0 ){
      /* printf("BINARY DATA AT BYTE %d\n", i); */
      /* goto gotNone; */
      *pOut = NULL;
      return FSL_RC_TYPE;
    }
    if( c=='\n' && z[i+1]!=0 ){
      nLine++;
      if( j>LENGTH_MASK ){
        /* printf("TOO LONG AT COLUMN %d\n", j); */
        /* goto gotNone; */
        *pOut = NULL;
        return FSL_RC_RANGE;
      }
      j = 0;
    }
  }
  if( j>LENGTH_MASK ){
    /* printf("TOO LONG AGAIN AT COLUMN %d\n", j); */
    /* goto gotNone; */
    *pOut = NULL;
    return FSL_RC_RANGE;
  }
  a = fsl_malloc( nLine*sizeof(a[0]) );
  if(!a) return FSL_RC_OOM;
  memset(a, 0, nLine*sizeof(a[0]) );
  if( n==0 ){
    /* gotNone: */
    *pnLine = 0;
    *pOut = a;
    return 0;
  }

  /* Fill in the array */
  for(i=0; i<nLine; i++){
    for(j=0; z[j] && z[j]!='\n'; j++){}
    a[i].z = z;
    k = j;
    if( diffFlags & DIFF_STRIP_EOLCR ){
      if( k>0 && z[k-1]=='\r' ){ k--; }
    }
    a[i].n = k;
    s = 0;
    if(diffFlags & DIFF_IGNORE_EOLWS){
      while( k>0 && fsl_isspace(z[k-1]) ){ k--; }
    }
    if( (diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){
      while( s<k && fsl_isspace(z[s]) ){ s++; }
    }
    a[i].indent = s;
    for(h=0, x=s; x<k; x++){
      h = h ^ (h<<2) ^ z[x];
    }
    a[i].h = h = (h<<LENGTH_MASK_SZ) | (k-s);
    h2 = h % nLine;
    a[i].iNext = a[h2].iHash;
    a[h2].iHash = i+1;
    z += j+1;
  }

  /* Return results */
  *pnLine = nLine;
  *pOut = a;
  return 0;
}


/*
   Return true if two DLine elements are identical.
*/
static int same_dline(DLine const *pA, DLine const *pB){
  return pA->h==pB->h && memcmp(pA->z+pA->indent,pB->z+pB->indent,
                                pA->h & LENGTH_MASK)==0;
}


/*
** Return true if two DLine elements are identical, ignoring
** all whitespace. The indent field of pA/pB already points
** to the first non-space character in the string.
*/
static int same_dline_ignore_allws(const DLine *pA, const DLine *pB){
  int a = pA->indent, b = pB->indent;
  if( pA->h==pB->h ){
    while( a<pA->n || b<pB->n ){
      if( a<pA->n && b<pB->n && pA->z[a++] != pB->z[b++] ) return 0;
      while( a<pA->n && fsl_isspace(pA->z[a])) ++a;
      while( b<pB->n && fsl_isspace(pB->z[b])) ++b;
    }
    return pA->n-a == pB->n-b;
  }
  return 0;
}

/*
   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.
*/
typedef struct DContext DContext;
struct DContext {
  int *aEdit;        /* Array of copy/delete/insert triples */
  int nEdit;         /* Number of integers (3x num of triples) in aEdit[] */
  int nEditAlloc;    /* Space allocated for aEdit[] */
  DLine *aFrom;      /* File on left side of the diff */
  int nFrom;         /* Number of lines in aFrom[] */
  DLine *aTo;        /* File on right side of the diff */
  int nTo;           /* Number of lines in aTo[] */
  int (*same_fn)(const DLine *, const DLine *); /* Function to be used for comparing */
};

/**
    Holds output state for diff generation.
 */
struct DiffOutState {
  /** Output callback. */
  fsl_output_f out;
  /** State for this->out(). */
  void * oState;
  /** For propagating output errors. */
  int rc;
  char ansiColor;
};
typedef struct DiffOutState DiffOutState;
static const DiffOutState DiffOutState_empty = {
NULL/*out*/,
NULL/*oState*/,
0/*rc*/,
0/*useAnsiColor*/
};

/**
    Internal helper. Sends src to o->out(). If n is negative, fsl_strlen()
    is used to determine the length.
 */
static int diff_out( DiffOutState * o, void const * src, fsl_int_t n ){
  return o->rc = n
    ? o->out(o->oState, src, (n<0)?fsl_strlen((char const *)src):(fsl_size_t)n)
    : 0;
}


/**
    fsl_appendf_f() impl for use with diff_outf(). state must be a
    (DiffOutState*).
 */
static fsl_int_t fsl_appendf_f_diff_out( void * state, char const * s,
                                         fsl_int_t n ){
  DiffOutState * os = (DiffOutState *)state;
  diff_out(os, s, n);
  return os->rc ? -1 : n;
}

static int diff_outf( DiffOutState * o, char const * fmt, ... ){
  va_list va;
  va_start(va,fmt);
  fsl_appendfv(fsl_appendf_f_diff_out, o, fmt, va);
  va_end(va);
  return o->rc;
}

/*
   Append a single line of context-diff output to pOut.
*/
static int appendDiffLine(
  DiffOutState *pOut,         /* Where to write the line of output */
  char cPrefix,       /* One of " ", "+",  or "-" */
  DLine *pLine,       /* The line to be output */
  int html,           /* True if generating HTML.  False for plain text */
  ReCompiled *pRe     /* Colorize only if line matches this Regex */
){
  int rc = 0;
  char const * ansiPrefix =
    !pOut->ansiColor
    ? NULL 
    : (('+'==cPrefix)
       ? ANSI_DIFF_ADD(0)
       : (('-'==cPrefix) ? ANSI_DIFF_RM(0) : NULL))
    ;
  if(ansiPrefix) rc = diff_out(pOut, ansiPrefix, -1 );
  if(!rc) rc = diff_out(pOut, &cPrefix, 1);
  if(rc) return rc;
  else if( html ){
#if 0
    if( pRe /*MISSING: && re_dline_match(pRe, pLine, 1)==0 */ ){
      cPrefix = ' ';
    }else
#endif
    if( cPrefix=='+' ){
      rc = diff_out(pOut, "<span class=\"fsl-diff-add\">", -1);
    }else if( cPrefix=='-' ){
      rc = diff_out(pOut, "<span class=\"fsl-diff-rm\">", -1);
    }
    if(!rc){
      /* unsigned short n = pLine->n; */
      /* while( n>0 && (pLine->z[n-1]=='\n' || pLine->z[n-1]=='\r') ) n--; */
      rc = pOut->rc = fsl_htmlize(pOut->out, pOut->oState,
                                  pLine->z, pLine->n);
      if( !rc && cPrefix!=' ' ){
        rc = diff_out(pOut, "</span>", -1);
      }
    }
  }else{
    rc = diff_out(pOut, pLine->z, pLine->n);
  }
  if(!rc){
    if(ansiPrefix){
      rc = diff_out(pOut, ANSI_RESET, -1 );
    }
    if(!rc) rc = diff_out(pOut, "\n", 1);
  }
  return rc;
}


/*
   Add two line numbers to the beginning of an output line for a context
   diff.  One or the other of the two numbers might be zero, which means
   to leave that number field blank.  The "html" parameter means to format
   the output for HTML.
*/
static int appendDiffLineno(DiffOutState *pOut, int lnA,
                            int lnB, int html){
  int rc = 0;
  if( html ){
    rc = diff_out(pOut, "<span class=\"fsl-diff-lineno\">", -1);
  }
  if(!rc){
    if( lnA>0 ){
      rc = diff_outf(pOut, "%6d ", lnA);
    }else{
      rc = diff_out(pOut, "       ", 7);
    }
  }
  if(!rc){
    if( lnB>0 ){
      rc = diff_outf(pOut, "%6d  ", lnB);
    }else{
      rc = diff_out(pOut, "        ", 8);
    }
    if( !rc && html ){
      rc = diff_out(pOut, "</span>", -1);
    }
  }
  return rc;
}


/*
   Minimum of two values
*/
#define minInt(a,b) (((a)<(b)) ? (a) : (b))


/**
    Compute the optimal longest common subsequence (LCS) using an
    exhaustive search.  This version of the LCS is only used for
    shorter input strings since runtime is O(N*N) where N is the
    input string length.
 */
static void optimalLCS(
  DContext *p,               /* Two files being compared */
  int iS1, int iE1,          /* Range of lines in p->aFrom[] */
  int iS2, int iE2,          /* Range of lines in p->aTo[] */
  int *piSX, int *piEX,      /* Write p->aFrom[] common segment here */
  int *piSY, int *piEY       /* Write p->aTo[] common segment here */
){
  int mxLength = 0;          /* Length of longest common subsequence */
  int i, j;                  /* Loop counters */
  int k;                     /* Length of a candidate subsequence */
  int iSXb = iS1;            /* Best match so far */
  int iSYb = iS2;            /* Best match so far */

  for(i=iS1; i<iE1-mxLength; i++){
    for(j=iS2; j<iE2-mxLength; j++){
      if( !p->same_fn(&p->aFrom[i], &p->aTo[j]) ) continue;
      if( mxLength && !p->same_fn(&p->aFrom[i+mxLength], &p->aTo[j+mxLength]) ){
        continue;
      }
      k = 1;
      while( i+k<iE1 && j+k<iE2 && p->same_fn(&p->aFrom[i+k],&p->aTo[j+k]) ){
        k++;
      }
      if( k>mxLength ){
        iSXb = i;
        iSYb = j;
        mxLength = k;
      }
    }
  }
  *piSX = iSXb;
  *piEX = iSXb + mxLength;
  *piSY = iSYb;
  *piEY = iSYb + mxLength;
}

/**
    Compare two blocks of text on lines iS1 through iE1-1 of the aFrom[]
    file and lines iS2 through iE2-1 of the aTo[] file.  Locate a sequence
    of lines in these two blocks that are exactly the same.  Return
    the bounds of the matching sequence.
   
    If there are two or more possible answers of the same length, the
    returned sequence should be the one closest to the center of the
    input range.
   
    Ideally, the common sequence should be the longest possible common
    sequence.  However, an exact computation of LCS is O(N*N) which is
    way too slow for larger files.  So this routine uses an O(N)
    heuristic approximation based on hashing that usually works about
    as well.  But if the O(N) algorithm doesn't get a good solution
    and N is not too large, we fall back to an exact solution by
    calling optimalLCS().
 */
static void longestCommonSequence(
  DContext *p,               /* Two files being compared */
  int iS1, int iE1,          /* Range of lines in p->aFrom[] */
  int iS2, int iE2,          /* Range of lines in p->aTo[] */
  int *piSX, int *piEX,      /* Write p->aFrom[] common segment here */
  int *piSY, int *piEY       /* Write p->aTo[] common segment here */
){
  int i, j, k;               /* Loop counters */
  int n;                     /* Loop limit */
  DLine *pA, *pB;            /* Pointers to lines */
  int iSX, iSY, iEX, iEY;    /* Current match */
  int skew = 0;              /* How lopsided is the match */
  int dist = 0;              /* Distance of match from center */
  int mid;                   /* Center of the span */
  int iSXb, iSYb, iEXb, iEYb;   /* Best match so far */
  int iSXp, iSYp, iEXp, iEYp;   /* Previous match */
  sqlite3_int64 bestScore;      /* Best score so far */
  sqlite3_int64 score;          /* Score for current candidate LCS */
  int span;                     /* combined width of the input sequences */

  span = (iE1 - iS1) + (iE2 - iS2);
  bestScore = -10000;
  score = 0;
  iSXb = iSXp = iS1;
  iEXb = iEXp = iS1;
  iSYb = iSYp = iS2;
  iEYb = iEYp = iS2;
  mid = (iE1 + iS1)/2;
  for(i=iS1; i<iE1; i++){
    int limit = 0;
    j = p->aTo[p->aFrom[i].h % p->nTo].iHash;
    while( j>0
      && (j-1<iS2 || j>=iE2 || !p->same_fn(&p->aFrom[i], &p->aTo[j-1]))
    ){
      if( limit++ > 10 ){
        j = 0;
        break;
      }
      j = p->aTo[j-1].iNext;
    }
    if( j==0 ) continue;
    assert( i>=iSXb && i>=iSXp );
    if( i<iEXb && j>=iSYb && j<iEYb ) continue;
    if( i<iEXp && j>=iSYp && j<iEYp ) continue;
    iSX = i;
    iSY = j-1;
    pA = &p->aFrom[iSX-1];
    pB = &p->aTo[iSY-1];
    n = minInt(iSX-iS1, iSY-iS2);
    for(k=0; k<n && p->same_fn(pA,pB); k++, pA--, pB--){}
    iSX -= k;
    iSY -= k;
    iEX = i+1;
    iEY = j;
    pA = &p->aFrom[iEX];
    pB = &p->aTo[iEY];
    n = minInt(iE1-iEX, iE2-iEY);
    for(k=0; k<n && p->same_fn(pA,pB); k++, pA++, pB++){}
    iEX += k;
    iEY += k;
    skew = (iSX-iS1) - (iSY-iS2);
    if( skew<0 ) skew = -skew;
    dist = (iSX+iEX)/2 - mid;
    if( dist<0 ) dist = -dist;
    score = (iEX - iSX)*(sqlite3_int64)span - (skew + dist);
    if( score>bestScore ){
      bestScore = score;
      iSXb = iSX;
      iSYb = iSY;
      iEXb = iEX;
      iEYb = iEY;
    }else if( iEX>iEXp ){
      iSXp = iSX;
      iSYp = iSY;
      iEXp = iEX;
      iEYp = iEY;
    }
  }
  if( iSXb==iEXb && (int64_t)(iE1-iS1)*(iE2-iS2)<400 ){
    /* If no common sequence is found using the hashing heuristic and
       the input is not too big, use the expensive exact solution */
    optimalLCS(p, iS1, iE1, iS2, iE2, piSX, piEX, piSY, piEY);
  }else{
    *piSX = iSXb;
    *piSY = iSYb;
    *piEX = iEXb;
    *piEY = iEYb;
  }
}

/**
    Expand the size of p->aEdit array to hold at least nEdit elements.
 */
static int expandEdit(DContext *p, int nEdit){
  void * re = fsl_realloc(p->aEdit, nEdit*sizeof(int));
  if(!re) return FSL_RC_OOM;
  else{
    p->aEdit = (int*)re;
    p->nEditAlloc = nEdit;
    return 0;
  }
}

/**
    Append a new COPY/DELETE/INSERT triple.
   
    Returns 0 on success, FSL_RC_OOM on OOM.
 */
static int appendTriple(DContext *p, int nCopy, int nDel, int nIns){
  /* printf("APPEND %d/%d/%d\n", nCopy, nDel, nIns); */
  if( p->nEdit>=3 ){
    if( p->aEdit[p->nEdit-1]==0 ){
      if( p->aEdit[p->nEdit-2]==0 ){
        p->aEdit[p->nEdit-3] += nCopy;
        p->aEdit[p->nEdit-2] += nDel;
        p->aEdit[p->nEdit-1] += nIns;
        return 0;
      }
      if( nCopy==0 ){
        p->aEdit[p->nEdit-2] += nDel;
        p->aEdit[p->nEdit-1] += nIns;
        return 0;
      }
    }
    if( nCopy==0 && nDel==0 ){
      p->aEdit[p->nEdit-1] += nIns;
      return 0;
    }
  }
  if( p->nEdit+3>p->nEditAlloc ){
    int const rc = expandEdit(p, p->nEdit*2 + 15);
    if(rc) return rc;
    else if( p->aEdit==0 ) return 0;
  }
  p->aEdit[p->nEdit++] = nCopy;
  p->aEdit[p->nEdit++] = nDel;
  p->aEdit[p->nEdit++] = nIns;
  return 0;
}


/**
    Do a single step in the difference.  Compute a sequence of
    copy/delete/insert steps that will convert lines iS1 through iE1-1
    of the input into lines iS2 through iE2-1 of the output and write
    that sequence into the difference context.
   
    The algorithm is to find a block of common text near the middle of
    the two segments being diffed.  Then recursively compute
    differences on the blocks before and after that common segment.
    Special cases apply if either input segment is empty or if the two
    segments have no text in common.
 */
static int diff_step(DContext *p, int iS1, int iE1, int iS2, int iE2){
  int iSX, iEX, iSY, iEY;
  int rc = 0;
  if( iE1<=iS1 ){
    /* The first segment is empty */
    if( iE2>iS2 ){
      rc = appendTriple(p, 0, 0, iE2-iS2);
    }
    return rc;
  }
  if( iE2<=iS2 ){
    /* The second segment is empty */
    return appendTriple(p, 0, iE1-iS1, 0);
  }

  /* Find the longest matching segment between the two sequences */
  longestCommonSequence(p, iS1, iE1, iS2, iE2, &iSX, &iEX, &iSY, &iEY);

  if( iEX>iSX ){
    /* A common segment has been found.
       Recursively diff either side of the matching segment */
    rc = diff_step(p, iS1, iSX, iS2, iSY);
    if( !rc){
      if(iEX>iSX){
        rc = appendTriple(p, iEX - iSX, 0, 0);
      }
      if(!rc) rc = diff_step(p, iEX, iE1, iEY, iE2);
    }
  }else{
    /* The two segments have nothing in common.  Delete the first then
       insert the second. */
    rc = appendTriple(p, 0, iE1-iS1, iE2-iS2);
  }
  return rc;
}

/**
    Compute the differences between two files already loaded into
    the DContext 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.
 */
static int diff_all(DContext *p){
  int mnE, iS, iE1, iE2;
  int rc = 0;
  /* Carve off the common header and footer */
  iE1 = p->nFrom;
  iE2 = p->nTo;
  while( iE1>0 && iE2>0 && p->same_fn(&p->aFrom[iE1-1], &p->aTo[iE2-1]) ){
    iE1--;
    iE2--;
  }
  mnE = iE1<iE2 ? iE1 : iE2;
  for(iS=0; iS<mnE && p->same_fn(&p->aFrom[iS],&p->aTo[iS]); iS++){}

  /* do the difference */
  if( iS>0 ){
    rc = appendTriple(p, iS, 0, 0);
    if(rc) return rc;
  }
  rc = diff_step(p, iS, iE1, iS, iE2);
  if(rc) return rc;
  if( iE1<p->nFrom ){
    rc = appendTriple(p, p->nFrom - iE1, 0, 0);
    if(rc) return rc;
  }

  /* Terminate the COPY/DELETE/INSERT triples with three zeros */
  rc = expandEdit(p, p->nEdit+3);
  if(rc) return rc;
  else if( p->aEdit ){
    p->aEdit[p->nEdit++] = 0;
    p->aEdit[p->nEdit++] = 0;
    p->aEdit[p->nEdit++] = 0;
  }
  return rc;
}


/*
   Attempt to shift insertion or deletion blocks so that they begin and
   end on lines that are pure whitespace.  In other words, try to transform
   this:
  
        int func1(int x){
           return x*10;
       +}
       +
       +int func2(int x){
       +   return x*20;
        }
  
        int func3(int x){
           return x/5;
        }
  
   Into one of these:
  
        int func1(int x){              int func1(int x){
           return x*10;                   return x*10;
        }                              }
       +
       +int func2(int x){             +int func2(int x){
       +   return x*20;               +   return x*20;
       +}                             +}
                                      +
        int func3(int x){              int func3(int x){
           return x/5;                    return x/5;
        }                              }
*/
static void diff_optimize(DContext *p){
  int r;       /* Index of current triple */
  int lnFrom;  /* Line number in p->aFrom */
  int lnTo;    /* Line number in p->aTo */
  int cpy, del, ins;

  lnFrom = lnTo = 0;
  for(r=0; r<p->nEdit; r += 3){
    cpy = p->aEdit[r];
    del = p->aEdit[r+1];
    ins = p->aEdit[r+2];
    lnFrom += cpy;
    lnTo += cpy;

    /* Shift insertions toward the beginning of the file */
    while( cpy>0 && del==0 && ins>0 ){
      DLine *pTop = &p->aFrom[lnFrom-1];  /* Line before start of insert */
      DLine *pBtm = &p->aTo[lnTo+ins-1];  /* Last line inserted */
      if( p->same_fn(pTop, pBtm)==0 ) break;
      if( LENGTH(pTop+1)+LENGTH(pBtm)<=LENGTH(pTop)+LENGTH(pBtm-1) ) break;
      lnFrom--;
      lnTo--;
      p->aEdit[r]--;
      p->aEdit[r+3]++;
      cpy--;
    }

    /* Shift insertions toward the end of the file */
    while( r+3<p->nEdit && p->aEdit[r+3]>0 && del==0 && ins>0 ){
      DLine *pTop = &p->aTo[lnTo];       /* First line inserted */
      DLine *pBtm = &p->aTo[lnTo+ins];   /* First line past end of insert */
      if( p->same_fn(pTop, pBtm)==0 ) break;
      if( LENGTH(pTop)+LENGTH(pBtm-1)<=LENGTH(pTop+1)+LENGTH(pBtm) ) break;
      lnFrom++;
      lnTo++;
      p->aEdit[r]++;
      p->aEdit[r+3]--;
      cpy++;
    }

    /* Shift deletions toward the beginning of the file */
    while( cpy>0 && del>0 && ins==0 ){
      DLine *pTop = &p->aFrom[lnFrom-1];     /* Line before start of delete */
      DLine *pBtm = &p->aFrom[lnFrom+del-1]; /* Last line deleted */
      if( p->same_fn(pTop, pBtm)==0 ) break;
      if( LENGTH(pTop+1)+LENGTH(pBtm)<=LENGTH(pTop)+LENGTH(pBtm-1) ) break;
      lnFrom--;
      lnTo--;
      p->aEdit[r]--;
      p->aEdit[r+3]++;
      cpy--;
    }

    /* Shift deletions toward the end of the file */
    while( r+3<p->nEdit && p->aEdit[r+3]>0 && del>0 && ins==0 ){
      DLine *pTop = &p->aFrom[lnFrom];     /* First line deleted */
      DLine *pBtm = &p->aFrom[lnFrom+del]; /* First line past end of delete */
      if( p->same_fn(pTop, pBtm)==0 ) break;
      if( LENGTH(pTop)+LENGTH(pBtm-1)<=LENGTH(pTop)+LENGTH(pBtm) ) break;
      lnFrom++;
      lnTo++;
      p->aEdit[r]++;
      p->aEdit[r+3]--;
      cpy++;
    }

    lnFrom += del;
    lnTo += ins;
  }
}


/*
   Given a raw diff p[] in which the p->aEdit[] array has been filled
   in, compute a context diff into pOut.
*/
static int contextDiff(
  DContext *p,      /* The difference */
  DiffOutState *pOut,       /* Output a context diff to here */
  ReCompiled *pRe,  /* Only show changes that match this regex */
  u64 diffFlags     /* Flags controlling the diff format */
){
  DLine *A;     /* Left side of the diff */
  DLine *B;     /* Right side of the diff */
  int a = 0;    /* Index of next line in A[] */
  int b = 0;    /* Index of next line in B[] */
  int *R;       /* Array of COPY/DELETE/INSERT triples */
  int r;        /* Index into R[] */
  int nr;       /* Number of COPY/DELETE/INSERT triples to process */
  int mxr;      /* Maximum value for r */
  int na, nb;   /* Number of lines shown from A and B */
  int i, j;     /* Loop counters */
  int m;        /* Number of lines to output */
  int skip;     /* Number of lines to skip */
  static int nChunk = 0;  /* Number of diff chunks seen so far */
  int nContext;    /* Number of lines of context */
  int showLn;      /* Show line numbers */
  int html;        /* Render as HTML */
  int showDivider = 0;  /* True to show the divider between diff blocks */
  int rc = 0;
  nContext = diff_context_lines(diffFlags);
  showLn = (diffFlags & DIFF_LINENO)!=0;
  html = (diffFlags & DIFF_HTML)!=0;
  A = p->aFrom;
  B = p->aTo;
  R = p->aEdit;
  mxr = p->nEdit;
  while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; }
  for(r=0; r<mxr; r += 3*nr){
    /* Figure out how many triples to show in a single block */
    for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){}
    /* printf("r=%d nr=%d\n", r, nr); */

    /* If there is a regex, skip this block (generate no diff output)
       if the regex matches or does not match both insert and delete.
       Only display the block if one side matches but the other side does
       not.
    */
#if 0
    /* MISSING: re. i would prefer a predicate function,
       anyway.
    */
    if( pRe ){
      int hideBlock = 1;
      int xa = a, xb = b;
      for(i=0; hideBlock && i<nr; i++){
        int c1, c2;
        xa += R[r+i*3];
        xb += R[r+i*3];
        c1 = re_dline_match(pRe, &A[xa], R[r+i*3+1]);
        c2 = re_dline_match(pRe, &B[xb], R[r+i*3+2]);
        hideBlock = c1==c2;
        xa += R[r+i*3+1];
        xb += R[r+i*3+2];
      }
      if( hideBlock ){
        a = xa;
        b = xb;
        continue;
      }
    }
#endif
    /* For the current block comprising nr triples, figure out
       how many lines of A and B are to be displayed
    */
    if( R[r]>nContext ){
      na = nb = nContext;
      skip = R[r] - nContext;
    }else{
      na = nb = R[r];
      skip = 0;
    }
    for(i=0; i<nr; i++){
      na += R[r+i*3+1];
      nb += R[r+i*3+2];
    }
    if( R[r+nr*3]>nContext ){
      na += nContext;
      nb += nContext;
    }else{
      na += R[r+nr*3];
      nb += R[r+nr*3];
    }
    for(i=1; i<nr; i++){
      na += R[r+i*3];
      nb += R[r+i*3];
    }

    /* Show the header for this block, or if we are doing a modified
       context diff that contains line numbers, show the separator from
       the previous block.
    */
    nChunk++;
    if( showLn ){
      if( !showDivider ){
        /* Do not show a top divider */
        showDivider = 1;
      }else if( html ){
        rc = diff_outf(pOut, "<span class=\"fsl-diff-hr\">%.80c</span>\n", '.');
      }else{
        rc = diff_outf(pOut, "%.80c\n", '.');
      }
      if( !rc && html ){
        rc = diff_outf(pOut, "<span class=\"fsl-diff-chunk-%d\"></span>", nChunk);
      }
    }else{
      char const * ansi1 = "";
      char const * ansi2 = "";
      char const * ansi3 = "";
      if( html ) rc = diff_outf(pOut, "<span class=\"fsl-diff-lineno\">");
      else if(0 && pOut->ansiColor){
        /* Turns out this just confuses the output */
        ansi1 = ANSI_DIFF_RM(0);
        ansi2 = ANSI_DIFF_ADD(0);
        ansi3 = ANSI_RESET;
      }
      /*
       * If the patch changes an empty file or results in an empty file,
       * the block header must use 0,0 as position indicator and not 1,0.
       * Otherwise, patch would be confused and may reject the diff.
       */
      if(!rc) rc = diff_outf(pOut,"@@ %s-%d,%d %s+%d,%d%s @@",
                             ansi1, na ? a+skip+1 : 0, na,
                             ansi2, nb ? b+skip+1 : 0, nb,
                             ansi3);
      if( !rc ){
        if( html ) rc = diff_outf(pOut, "</span>");
        if(!rc) rc = diff_out(pOut, "\n", 1);
      }
    }
    if(rc) return rc;

    /* Show the initial common area */
    a += skip;
    b += skip;
    m = R[r] - skip;
    for(j=0; !rc && j<m; j++){
      if( showLn ) rc = appendDiffLineno(pOut, a+j+1, b+j+1, html);
      if(!rc) rc = appendDiffLine(pOut, ' ', &A[a+j], html, 0);
    }
    if(rc) return rc;
    a += m;
    b += m;

    /* Show the differences */
    for(i=0; i<nr; i++){
      m = R[r+i*3+1];
      for(j=0; !rc && j<m; j++){
        if( showLn ) rc = appendDiffLineno(pOut, a+j+1, 0, html);
        if(!rc) rc = appendDiffLine(pOut, '-', &A[a+j], html, pRe);
      }
      if(rc) return rc;
      a += m;
      m = R[r+i*3+2];
      for(j=0; !rc && j<m; j++){
        if( showLn ) rc = appendDiffLineno(pOut, 0, b+j+1, html);
        if(!rc) rc = appendDiffLine(pOut, '+', &B[b+j], html, pRe);
      }
      if(rc) return rc;
      b += m;
      if( i<nr-1 ){
        m = R[r+i*3+3];
        for(j=0; !rc && j<m; j++){
          if( showLn ) rc = appendDiffLineno(pOut, a+j+1, b+j+1, html);
          if(!rc) rc = appendDiffLine(pOut, ' ', &A[a+j], html, 0);
        }
        if(rc) return rc;
        b += m;
        a += m;
      }
    }

    /* Show the final common area */
    assert( nr==i );
    m = R[r+nr*3];
    if( m>nContext ) m = nContext;
    for(j=0; !rc && j<m; j++){
      if( showLn ) rc = appendDiffLineno(pOut, a+j+1, b+j+1, html);
      if(!rc) rc = appendDiffLine(pOut, ' ', &A[a+j], html, 0);
    }
  }/*big for() loop*/
  return rc;
}

/*
   Status of a single output line
*/
typedef struct SbsLine SbsLine;
struct SbsLine {
  fsl_buffer *apCols[5];   /* Array of pointers to output columns */
  int width;               /* Maximum width of a column in the output */
  unsigned char escHtml;   /* True to escape html characters */
  int iStart;              /* Write zStart prior to character iStart */
  const char *zStart;      /* A <span> tag */
  int iEnd;                /* Write </span> prior to character iEnd */
  int iStart2;             /* Write zStart2 prior to character iStart2 */
  const char *zStart2;     /* A <span> tag */
  int iEnd2;               /* Write </span> prior to character iEnd2 */
  ReCompiled *pRe;         /* Only colorize matching lines, if not NULL */
  DiffOutState * pOut;
};

/*
   Column indices for SbsLine.apCols[]
*/
#define SBS_LNA  0     /* Left line number */
#define SBS_TXTA 1     /* Left text */
#define SBS_MKR  2     /* Middle separator column */
#define SBS_LNB  3     /* Right line number */
#define SBS_TXTB 4     /* Right text */

/*
   Append newlines to all columns.
*/
static int sbsWriteNewlines(SbsLine *p){
  int i;
  int rc = 0;
  for( i=p->escHtml ? SBS_LNA : SBS_TXTB; !rc && i<=SBS_TXTB; i++ ){
    rc = fsl_buffer_append(p->apCols[i], "\n", 1);
  }
  return rc;
}

/*
   Append n spaces to the column.
*/
static int sbsWriteSpace(SbsLine *p, int n, int col){
  return fsl_buffer_appendf(p->apCols[col], "%*s", n, "");
}

/*
   Write the text of pLine into column iCol of p.
  
   If outputting HTML, write the full line.  Otherwise, only write the
   width characters.  Translate tabs into spaces.  Add newlines if col
   is SBS_TXTB.  Translate HTML characters if escHtml is true.  Pad the
   rendering to width bytes if col is SBS_TXTA and escHtml is false.
  
   This comment contains multibyte unicode characters (�, �, �) in order
   to test the ability of the diff code to handle such characters.
*/
static int sbsWriteText(SbsLine *p, DLine *pLine, int col){
  fsl_buffer *pCol = p->apCols[col];
  int rc = 0;
  int n = pLine->n;
  int i;   /* Number of input characters consumed */
  int k;   /* Cursor position */
  int needEndSpan = 0;
  const char *zIn = pLine->z;
  int w = p->width;
  int colorize = p->escHtml;
#if 0
  /* MISSING: re bits, but want to replace those with a predicate. */
  if( colorize && p->pRe && re_dline_match(p->pRe, pLine, 1)==0 ){
    colorize = 0;
  }
#endif
  for(i=k=0; !rc && (p->escHtml || k<w) && i<n; i++, k++){
    char c = zIn[i];
    if( colorize ){
      if( i==p->iStart ){
        rc = fsl_buffer_append(pCol, p->zStart, -1);
        if(rc) break;
        needEndSpan = 1;
        if( p->iStart2 ){
          p->iStart = p->iStart2;
          p->zStart = p->zStart2;
          p->iStart2 = 0;
        }
      }else if( i==p->iEnd ){
        rc = fsl_buffer_append(pCol, "</span>", 7);
        if(rc) break;
        needEndSpan = 0;
        if( p->iEnd2 ){
          p->iEnd = p->iEnd2;
          p->iEnd2 = 0;
        }
      }
    }
    if( c=='\t' && !p->escHtml ){
      rc = fsl_buffer_append(pCol, " ", 1);
      while( !rc && (k&7)!=7 && (p->escHtml || k<w) ){
        rc = fsl_buffer_append(pCol, " ", 1);
        k++;
      }
    }else if( c=='\r' || c=='\f' ){
      rc = fsl_buffer_append(pCol, " ", 1);
    }else if( c=='<' && p->escHtml ){
      rc = fsl_buffer_append(pCol, "&lt;", 4);
    }else if( c=='&' && p->escHtml ){
      rc = fsl_buffer_append(pCol, "&amp;", 5);
    }else if( c=='>' && p->escHtml ){
      rc = fsl_buffer_append(pCol, "&gt;", 4);
    }else if( c=='"' && p->escHtml ){
      rc = fsl_buffer_append(pCol, "&quot;", 6);
    }else{
      rc = fsl_buffer_append(pCol, &zIn[i], 1);
      if( (c&0xc0)==0x80 ) k--;
    }
  }
  if( !rc && needEndSpan ){
    rc = fsl_buffer_append(pCol, "</span>", 7);
  }
  if(!rc){
    if( col==SBS_TXTB ){
      rc = sbsWriteNewlines(p);
    }else if( !p->escHtml ){
      rc = sbsWriteSpace(p, w-k, SBS_TXTA);
    }
  }
  return rc;
}


/*
   Append a column to the final output blob.
*/
static int sbsWriteColumn(DiffOutState *pOut, fsl_buffer const *pCol, int col){
  return diff_outf(pOut,
    "<td><div class=\"fsl-diff-%s-col\">\n"
    "<pre>\n"
    "%b"
    "</pre>\n"
    "</div></td>\n",
    col % 3 ? (col == SBS_MKR ? "separator" : "text") : "lineno",
    pCol
  );
}


/*
   Append a separator line to column iCol
*/
static int sbsWriteSep(SbsLine *p, int len, int col){
  char ch = '.';
  if( len<1 ){
    len = 1;
    ch = ' ';
  }
  return fsl_buffer_appendf(p->apCols[col],
                            "<span class=\"fsl-diff-hr\">%.*c</span>\n",
                            len, ch);
}


/*
   Append the appropriate marker into the center column of the diff.
*/
static int sbsWriteMarker(SbsLine *p, const char *zTxt,
                                 const char *zHtml){
  return fsl_buffer_append(p->apCols[SBS_MKR], p->escHtml ? zHtml : zTxt, -1);
}

/*
   Append a line number to the column.
*/
static int sbsWriteLineno(SbsLine *p, int ln, int col){
  int rc;
  if( p->escHtml ){
    rc = fsl_buffer_appendf(p->apCols[col], "%d", ln+1);
  }else{
    char zLn[8];
    fsl_snprintf(zLn, 8, "%5d ", ln+1);
    rc = fsl_buffer_appendf(p->apCols[col], "%s ", zLn);
  }
  return rc;
}


/*
   The two text segments zLeft and zRight are known to be different on
   both ends, but they might have  a common segment in the middle.  If
   they do not have a common segment, return 0.  If they do have a large
   common segment, return 1 and before doing so set:
  
     aLCS[0] = start of the common segment in zLeft
     aLCS[1] = end of the common segment in zLeft
     aLCS[2] = start of the common segment in zLeft
     aLCS[3] = end of the common segment in zLeft
  
   This computation is for display purposes only and does not have to be
   optimal or exact.
*/
static int textLCS(
  const char *zLeft,  int nA,       /* String on the left */
  const char *zRight,  int nB,      /* String on the right */
  int *aLCS                         /* Identify bounds of LCS here */
){
  const unsigned char *zA = (const unsigned char*)zLeft;    /* left string */
  const unsigned char *zB = (const unsigned char*)zRight;   /* right string */
  int nt;                    /* Number of target points */
  int ti[3];                 /* Index for start of each 4-byte target */
  unsigned int target[3];    /* 4-byte alignment targets */
  unsigned int probe;        /* probe to compare against target */
  int iAS, iAE, iBS, iBE;    /* Range of common segment */
  int i, j;                  /* Loop counters */
  int rc = 0;                /* Result code.  1 for success */

  if( nA<6 || nB<6 ) return 0;
  memset(aLCS, 0, sizeof(int)*4);
  ti[0] = i = nB/2-2;
  target[0] = (zB[i]<<24) | (zB[i+1]<<16) | (zB[i+2]<<8) | zB[i+3];
  probe = 0;
  if( nB<16 ){
    nt = 1;
  }else{
    ti[1] = i = nB/4-2;
    target[1] = (zB[i]<<24) | (zB[i+1]<<16) | (zB[i+2]<<8) | zB[i+3];
    ti[2] = i = (nB*3)/4-2;
    target[2] = (zB[i]<<24) | (zB[i+1]<<16) | (zB[i+2]<<8) | zB[i+3];
    nt = 3;
  }
  probe = (zA[0]<<16) | (zA[1]<<8) | zA[2];
  for(i=3; i<nA; i++){
    probe = (probe<<8) | zA[i];
    for(j=0; j<nt; j++){
      if( probe==target[j] ){
        iAS = i-3;
        iAE = i+1;
        iBS = ti[j];
        iBE = ti[j]+4;
        while( iAE<nA && iBE<nB && zA[iAE]==zB[iBE] ){ iAE++; iBE++; }
        while( iAS>0 && iBS>0 && zA[iAS-1]==zB[iBS-1] ){ iAS--; iBS--; }
        if( iAE-iAS > aLCS[1] - aLCS[0] ){
          aLCS[0] = iAS;
          aLCS[1] = iAE;
          aLCS[2] = iBS;
          aLCS[3] = iBE;
          rc = 1;
        }
      }
    }
  }
  return rc;
}


/*
   Try to shift iStart as far as possible to the left.
*/
static void sbsShiftLeft(SbsLine *p, const char *z){
  int i, j;
  while( (i=p->iStart)>0 && z[i-1]==z[i] ){
    for(j=i+1; j<p->iEnd && z[j-1]==z[j]; j++){}
    if( j<p->iEnd ) break;
    p->iStart--;
    p->iEnd--;
  }
}

/*
   Simplify iStart and iStart2:
  
      *  If iStart is a null-change then move iStart2 into iStart
      *  Make sure any null-changes are in canonoical form.
      *  Make sure all changes are at character boundaries for
         multi-byte characters.
*/
static void sbsSimplifyLine(SbsLine *p, const char *z){
  if( p->iStart2==p->iEnd2 ){
    p->iStart2 = p->iEnd2 = 0;
  }else if( p->iStart2 ){
    while( p->iStart2>0 && (z[p->iStart2]&0xc0)==0x80 ) p->iStart2--;
    while( (z[p->iEnd2]&0xc0)==0x80 ) p->iEnd2++;
  }
  if( p->iStart==p->iEnd ){
    p->iStart = p->iStart2;
    p->iEnd = p->iEnd2;
    p->zStart = p->zStart2;
    p->iStart2 = 0;
    p->iEnd2 = 0;
  }
  if( p->iStart==p->iEnd ){
    p->iStart = p->iEnd = -1;
  }else if( p->iStart>0 ){
    while( p->iStart>0 && (z[p->iStart]&0xc0)==0x80 ) p->iStart--;
    while( (z[p->iEnd]&0xc0)==0x80 ) p->iEnd++;
  }
}


/*
   Write out lines that have been edited.  Adjust the highlight to cover
   only those parts of the line that actually changed.
*/
static int sbsWriteLineChange(
  SbsLine *p,          /* The SBS output line */
  DLine *pLeft,        /* Left line of the change */
  int lnLeft,          /* Line number for the left line */
  DLine *pRight,       /* Right line of the change */
  int lnRight          /* Line number of the right line */
){
  int rc = 0;
  int nLeft;           /* Length of left line in bytes */
  int nRight;          /* Length of right line in bytes */
  int nShort;          /* Shortest of left and right */
  int nPrefix;         /* Length of common prefix */
  int nSuffix;         /* Length of common suffix */
  const char *zLeft;   /* Text of the left line */
  const char *zRight;  /* Text of the right line */
  int nLeftDiff;       /* nLeft - nPrefix - nSuffix */
  int nRightDiff;      /* nRight - nPrefix - nSuffix */
  int aLCS[4];         /* Bounds of common middle segment */
  static const char zClassRm[]   = "<span class=\"fsl-diff-rm\">";
  static const char zClassAdd[]  = "<span class=\"fsl-diff-add\">";
  static const char zClassChng[] = "<span class=\"fsl-diff-change\">";

  nLeft = pLeft->n;
  zLeft = pLeft->z;
  nRight = pRight->n;
  zRight = pRight->z;
  nShort = nLeft<nRight ? nLeft : nRight;

  nPrefix = 0;
  while( nPrefix<nShort && zLeft[nPrefix]==zRight[nPrefix] ){
    nPrefix++;
  }
  if( nPrefix<nShort ){
    while( nPrefix>0 && (zLeft[nPrefix]&0xc0)==0x80 ) nPrefix--;
  }
  nSuffix = 0;
  if( nPrefix<nShort ){
    while( nSuffix<nShort && zLeft[nLeft-nSuffix-1]==zRight[nRight-nSuffix-1] ){
      nSuffix++;
    }
    if( nSuffix<nShort ){
      while( nSuffix>0 && (zLeft[nLeft-nSuffix]&0xc0)==0x80 ) nSuffix--;
    }
    if( nSuffix==nLeft || nSuffix==nRight ) nPrefix = 0;
  }
  if( nPrefix+nSuffix > nShort ) nPrefix = nShort - nSuffix;

  /* A single chunk of text inserted on the right */
  if( nPrefix+nSuffix==nLeft ){
    rc = sbsWriteLineno(p, lnLeft, SBS_LNA);
    if(rc) return rc;
    p->iStart2 = p->iEnd2 = 0;
    p->iStart = p->iEnd = -1;
    rc = sbsWriteText(p, pLeft, SBS_TXTA);
    if( !rc && nLeft==nRight && zLeft[nLeft]==zRight[nRight] ){
      rc = sbsWriteMarker(p, "   ", "");
    }else{
      rc = sbsWriteMarker(p, " | ", "|");
    }
    if(!rc){
      rc = sbsWriteLineno(p, lnRight, SBS_LNB);
      if(!rc){
        p->iStart = nPrefix;
        p->iEnd = nRight - nSuffix;
        p->zStart = zClassAdd;
        rc = sbsWriteText(p, pRight, SBS_TXTB);
      }
    }
    return rc;
  }

  /* A single chunk of text deleted from the left */
  if( nPrefix+nSuffix==nRight ){
    /* Text deleted from the left */
    rc = sbsWriteLineno(p, lnLeft, SBS_LNA);
    if(!rc){
      p->iStart2 = p->iEnd2 = 0;
      p->iStart = nPrefix;
      p->iEnd = nLeft - nSuffix;
      p->zStart = zClassRm;
      rc = sbsWriteText(p, pLeft, SBS_TXTA);
      if(!rc){
        rc = sbsWriteMarker(p, " | ", "|");
        if(!rc){
          rc = sbsWriteLineno(p, lnRight, SBS_LNB);
          if(!rc){
            p->iStart = p->iEnd = -1;
            sbsWriteText(p, pRight, SBS_TXTB);
          }
        }
      }
    }
    return rc;
  }

  /* At this point we know that there is a chunk of text that has
     changed between the left and the right.  Check to see if there
     is a large unchanged section in the middle of that changed block.
  */
  nLeftDiff = nLeft - nSuffix - nPrefix;
  nRightDiff = nRight - nSuffix - nPrefix;
  if( p->escHtml
   && nLeftDiff >= 6
   && nRightDiff >= 6
   && textLCS(&zLeft[nPrefix], nLeftDiff, &zRight[nPrefix], nRightDiff, aLCS)
  ){
    rc = sbsWriteLineno(p, lnLeft, SBS_LNA);
    if(rc) return rc;
    p->iStart = nPrefix;
    p->iEnd = nPrefix + aLCS[0];
    if( aLCS[2]==0 ){
      sbsShiftLeft(p, pLeft->z);
      p->zStart = zClassRm;
    }else{
      p->zStart = zClassChng;
    }
    p->iStart2 = nPrefix + aLCS[1];
    p->iEnd2 = nLeft - nSuffix;
    p->zStart2 = aLCS[3]==nRightDiff ? zClassRm : zClassChng;
    sbsSimplifyLine(p, zLeft+nPrefix);
    rc = sbsWriteText(p, pLeft, SBS_TXTA);
    if(!rc) rc = sbsWriteMarker(p, " | ", "|");
    if(!rc) rc = sbsWriteLineno(p, lnRight, SBS_LNB);
    if(rc) return rc;
    p->iStart = nPrefix;
    p->iEnd = nPrefix + aLCS[2];
    if( aLCS[0]==0 ){
      sbsShiftLeft(p, pRight->z);
      p->zStart = zClassAdd;
    }else{
      p->zStart = zClassChng;
    }
    p->iStart2 = nPrefix + aLCS[3];
    p->iEnd2 = nRight - nSuffix;
    p->zStart2 = aLCS[1]==nLeftDiff ? zClassAdd : zClassChng;
    sbsSimplifyLine(p, zRight+nPrefix);
    rc = sbsWriteText(p, pRight, SBS_TXTB);
    return rc;
  }

  /* If all else fails, show a single big change between left and right */
  rc = sbsWriteLineno(p, lnLeft, SBS_LNA);
  if(!rc){
    p->iStart2 = p->iEnd2 = 0;
    p->iStart = nPrefix;
    p->iEnd = nLeft - nSuffix;
    p->zStart = zClassChng;
    rc = sbsWriteText(p, pLeft, SBS_TXTA);
    if(!rc){
      rc = sbsWriteMarker(p, " | ", "|");
      if(!rc){
        rc = sbsWriteLineno(p, lnRight, SBS_LNB);
        if(!rc){
          p->iEnd = nRight - nSuffix;
          sbsWriteText(p, pRight, SBS_TXTB);
        }
      }
    }
  }
  return rc;
}


/*
   Return the number between 0 and 100 that is smaller the closer pA and
   pB match.  Return 0 for a perfect match.  Return 100 if pA and pB are
   completely different.
  
   The current algorithm is as follows:
  
   (1) Remove leading and trailing whitespace.
   (2) Truncate both strings to at most 250 characters
   (3) Find the length of the longest common subsequence
   (4) Longer common subsequences yield lower scores.
*/
static int match_dline(DLine *pA, DLine *pB){
  const char *zA;            /* Left string */
  const char *zB;            /* right string */
  int nA;                    /* Bytes in zA[] */
  int nB;                    /* Bytes in zB[] */
  int avg;                   /* Average length of A and B */
  int i, j, k;               /* Loop counters */
  int best = 0;              /* Longest match found so far */
  int score;                 /* Final score.  0..100 */
  unsigned char c;           /* Character being examined */
  unsigned char aFirst[256]; /* aFirst[X] = index in zB[] of first char X */
  unsigned char aNext[252];  /* aNext[i] = index in zB[] of next zB[i] char */

  zA = pA->z;
  zB = pB->z;
  nA = pA->n;
  nB = pB->n;
  while( nA>0 && fsl_isspace(zA[0]) ){ nA--; zA++; }
  while( nA>0 && fsl_isspace(zA[nA-1]) ){ nA--; }
  while( nB>0 && fsl_isspace(zB[0]) ){ nB--; zB++; }
  while( nB>0 && fsl_isspace(zB[nB-1]) ){ nB--; }
  if( nA>250 ) nA = 250;
  if( nB>250 ) nB = 250;
  avg = (nA+nB)/2;
  if( avg==0 ) return 0;
  if( nA==nB && memcmp(zA, zB, nA)==0 ) return 0;
  memset(aFirst, 0, sizeof(aFirst));
  zA--; zB--;   /* Make both zA[] and zB[] 1-indexed */
  for(i=nB; i>0; i--){
    c = (unsigned char)zB[i];
    aNext[i] = aFirst[c];
    aFirst[c] = i;
  }
  best = 0;
  for(i=1; i<=nA-best; i++){
    c = (unsigned char)zA[i];
    for(j=aFirst[c]; j>0 && j<nB-best; j = aNext[j]){
      int limit = minInt(nA-i, nB-j);
      for(k=1; k<=limit && zA[k+i]==zB[k+j]; k++){}
      if( k>best ) best = k;
    }
  }
  score = (best>avg) ? 0 : (avg - best)*100/avg;

#if 0
  fprintf(stderr, "A: [%.*s]\nB: [%.*s]\nbest=%d avg=%d score=%d\n",
  nA, zA+1, nB, zB+1, best, avg, score);
#endif

  /* Return the result */
  return score;
}


/*
   There is a change block in which nLeft lines of text on the left are
   converted into nRight lines of text on the right.  This routine computes
   how the lines on the left line up with the lines on the right.
  
   The return value is a buffer of unsigned characters, obtained from
   fsl_malloc().  (The caller needs to free the return value using
   fsl_free().)  Entries in the returned array have values as follows:
  
      1.  Delete the next line of pLeft.
      2.  Insert the next line of pRight.
      3.  The next line of pLeft changes into the next line of pRight.
      4.  Delete one line from pLeft and add one line to pRight.
  
   Values larger than three indicate better matches.
  
   The length of the returned array will be just large enough to cause
   all elements of pLeft and pRight to be consumed.
  
   Algorithm:  Wagner's minimum edit-distance algorithm, modified by
   adding a cost to each match based on how well the two rows match
   each other.  Insertion and deletion costs are 50.  Match costs
   are between 0 and 100 where 0 is a perfect match 100 is a complete
   mismatch.
*/
static unsigned char *sbsAlignment(
   DLine *aLeft, int nLeft,       /* Text on the left */
   DLine *aRight, int nRight      /* Text on the right */
){
  int i, j, k;                 /* Loop counters */
  int *a;                      /* One row of the Wagner matrix */
  int *pToFree;                /* Space that needs to be freed */
  unsigned char *aM;           /* Wagner result matrix */
  int nMatch, iMatch;          /* Number of matching lines and match score */
  int mnLen;                   /* MIN(nLeft, nRight) */
  int mxLen;                   /* MAX(nLeft, nRight) */
  int aBuf[100];               /* Stack space for a[] if nRight not to big */

  aM = (unsigned char *)fsl_malloc( (nLeft+1)*(nRight+1) );
  if(!aM) return NULL;
  if( nLeft==0 ){
    memset(aM, 2, nRight);
    return aM;
  }
  if( nRight==0 ){
    memset(aM, 1, nLeft);
    return aM;
  }

  /* This algorithm is O(N**2).  So if N is too big, bail out with a
     simple (but stupid and ugly) result that doesn't take too long. */
  mnLen = nLeft<nRight ? nLeft : nRight;
  if( nLeft*nRight>100000 ){
    memset(aM, 4, mnLen);
    if( nLeft>mnLen )  memset(aM+mnLen, 1, nLeft-mnLen);
    if( nRight>mnLen ) memset(aM+mnLen, 2, nRight-mnLen);
    return aM;
  }

  if( nRight < (int)(sizeof(aBuf)/sizeof(aBuf[0]))-1 ){
    pToFree = 0;
    a = aBuf;
  }else{
    a = pToFree = fsl_malloc( sizeof(a[0])*(nRight+1) );
    if(!a){
      fsl_free(aM);
      return NULL;
    }
  }

  /* Compute the best alignment */
  for(i=0; i<=nRight; i++){
    aM[i] = 2;
    a[i] = i*50;
  }
  aM[0] = 0;
  for(j=1; j<=nLeft; j++){
    int p = a[0];
    a[0] = p+50;
    aM[j*(nRight+1)] = 1;
    for(i=1; i<=nRight; i++){
      int m = a[i-1]+50;
      int d = 2;
      if( m>a[i]+50 ){
        m = a[i]+50;
        d = 1;
      }
      if( m>p ){
        int score = match_dline(&aLeft[j-1], &aRight[i-1]);
        if( (score<=63 || (i<j+1 && i>j-1)) && m>p+score ){
          m = p+score;
          d = 3 | score*4;
        }
      }
      p = a[i];
      a[i] = m;
      aM[j*(nRight+1)+i] = d;
    }
  }

  /* Compute the lowest-cost path back through the matrix */
  i = nRight;
  j = nLeft;
  k = (nRight+1)*(nLeft+1)-1;
  nMatch = iMatch = 0;
  while( i+j>0 ){
    unsigned char c = aM[k];
    if( c>=3 ){
      assert( i>0 && j>0 );
      i--;
      j--;
      nMatch++;
      iMatch += (c>>2);
      aM[k] = 3;
    }else if( c==2 ){
      assert( i>0 );
      i--;
    }else{
      assert( j>0 );
      j--;
    }
    k--;
    aM[k] = aM[j*(nRight+1)+i];
  }
  k++;
  i = (nRight+1)*(nLeft+1) - k;
  memmove(aM, &aM[k], i);

  /* If:
       (1) the alignment is more than 25% longer than the longest side, and
       (2) the average match cost exceeds 15
     Then this is probably an alignment that will be difficult for humans
     to read.  So instead, just show all of the right side inserted followed
     by all of the left side deleted.
    
     The coefficients for conditions (1) and (2) above are determined by
     experimentation.
  */
  mxLen = nLeft>nRight ? nLeft : nRight;
  if( i*4>mxLen*5 && (nMatch==0 || iMatch/nMatch>15) ){
    memset(aM, 4, mnLen);
    if( nLeft>mnLen )  memset(aM+mnLen, 1, nLeft-mnLen);
    if( nRight>mnLen ) memset(aM+mnLen, 2, nRight-mnLen);
  }

  /* Return the result */
  fsl_free(pToFree);
  return aM;
}

/*
   R[] is an array of six integer, two COPY/DELETE/INSERT triples for a
   pair of adjacent differences.  Return true if the gap between these
   two differences is so small that they should be rendered as a single
   edit.
*/
static int smallGap(int *R){
  return R[3]<=2 || R[3]<=(R[1]+R[2]+R[4]+R[5])/8;
}

/*
   Given a diff context in which the aEdit[] array has been filled
   in, compute a side-by-side diff into pOut.
*/
static int sbsDiff(
  DContext *p,       /* The computed diff */
  DiffOutState *pOut,        /* Write the results here */
  ReCompiled *pRe,   /* Only show changes that match this regex */
  u64 diffFlags      /* Flags controlling the diff */
){
  DLine *A;     /* Left side of the diff */
  DLine *B;     /* Right side of the diff */
  int rc = 0;
  int a = 0;    /* Index of next line in A[] */
  int b = 0;    /* Index of next line in B[] */
  int *R;       /* Array of COPY/DELETE/INSERT triples */
  int r;        /* Index into R[] */
  int nr;       /* Number of COPY/DELETE/INSERT triples to process */
  int mxr;      /* Maximum value for r */
  int na, nb;   /* Number of lines shown from A and B */
  int i, j;     /* Loop counters */
  int m, ma, mb;/* Number of lines to output */
  int skip;     /* Number of lines to skip */
  static int nChunk = 0; /* Number of chunks of diff output seen so far */
  SbsLine s;    /* Output line buffer */
  int nContext; /* Lines of context above and below each change */
  int showDivider = 0;  /* True to show the divider */
  fsl_buffer unesc = fsl_buffer_empty;
  fsl_buffer aCols[5] = { /* Array of column blobs */
  fsl_buffer_empty_m, fsl_buffer_empty_m, fsl_buffer_empty_m,
  fsl_buffer_empty_m, fsl_buffer_empty_m
  }; 

  memset(&s, 0, sizeof(s));
  s.pOut = pOut;
  s.width = diff_width(diffFlags);
  nContext = diff_context_lines(diffFlags);
  s.escHtml = (diffFlags & DIFF_HTML)!=0;
  if( s.escHtml ){
    for(i=SBS_LNA; i<=SBS_TXTB; i++){
      s.apCols[i] = &aCols[i];
    }
  }else{
    for(i=SBS_LNA; i<=SBS_TXTB; i++){
      s.apCols[i] = &unesc;
    }
  }
  s.pRe = pRe;
  s.iStart = -1;
  s.iStart2 = 0;
  s.iEnd = -1;
  A = p->aFrom;
  B = p->aTo;
  R = p->aEdit;
  mxr = p->nEdit;
  while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; }

  for(r=0; r<mxr; r += 3*nr){
    /* Figure out how many triples to show in a single block */
    for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){}
    /* printf("r=%d nr=%d\n", r, nr); */
#if 0
    /* MISSING: re/predicate bits. */
    /* If there is a regex, skip this block (generate no diff output)
       if the regex matches or does not match both insert and delete.
       Only display the block if one side matches but the other side does
       not.
    */
    if( pRe ){
      int hideBlock = 1;
      int xa = a, xb = b;
      for(i=0; hideBlock && i<nr; i++){
        int c1, c2;
        xa += R[r+i*3];
        xb += R[r+i*3];
        c1 = re_dline_match(pRe, &A[xa], R[r+i*3+1]);
        c2 = re_dline_match(pRe, &B[xb], R[r+i*3+2]);
        hideBlock = c1==c2;
        xa += R[r+i*3+1];
        xb += R[r+i*3+2];
      }
      if( hideBlock ){
        a = xa;
        b = xb;
        continue;
      }
    }
#endif
    /* For the current block comprising nr triples, figure out
       how many lines of A and B are to be displayed
    */
    if( R[r]>nContext ){
      na = nb = nContext;
      skip = R[r] - nContext;
    }else{
      na = nb = R[r];
      skip = 0;
    }
    for(i=0; i<nr; i++){
      na += R[r+i*3+1];
      nb += R[r+i*3+2];
    }
    if( R[r+nr*3]>nContext ){
      na += nContext;
      nb += nContext;
    }else{
      na += R[r+nr*3];
      nb += R[r+nr*3];
    }
    for(i=1; i<nr; i++){
      na += R[r+i*3];
      nb += R[r+i*3];
    }

    /* Draw the separator between blocks */
    if( showDivider ){
      if( s.escHtml ){
        char zLn[10];
        fsl_snprintf(zLn, sizeof(zLn), "%d", a+skip+1);
        rc = sbsWriteSep(&s, strlen(zLn), SBS_LNA);
        if(!rc) rc = sbsWriteSep(&s, s.width, SBS_TXTA);
        if(!rc) rc = sbsWriteSep(&s, 0, SBS_MKR);
        if(!rc){
          fsl_snprintf(zLn, sizeof(zLn), "%d", b+skip+1);
          rc = sbsWriteSep(&s, strlen(zLn), SBS_LNB);
          if(!rc) rc = sbsWriteSep(&s, s.width, SBS_TXTB);
        }
      }else{
        diff_outf(pOut, "%.*c\n", s.width*2+16, '.');
      }
      if(rc) goto end;
    }
    showDivider = 1;
    nChunk++;
    if( s.escHtml ){
      rc = fsl_buffer_appendf(s.apCols[SBS_LNA],
                              "<span class=\"fsl-diff-chunk-%d\"></span>",
                              nChunk);
    }

    /* Show the initial common area */
    a += skip;
    b += skip;
    m = R[r] - skip;
    for(j=0; !rc && j<m; j++){
      rc = sbsWriteLineno(&s, a+j, SBS_LNA);
      if(rc) break;
      s.iStart = s.iEnd = -1;
      rc = sbsWriteText(&s, &A[a+j], SBS_TXTA);
      if(!rc) rc = sbsWriteMarker(&s, "   ", "");
      if(!rc) rc = sbsWriteLineno(&s, b+j, SBS_LNB);
      if(!rc) rc = sbsWriteText(&s, &B[b+j], SBS_TXTB);
    }
    if(rc) goto end;
    a += m;
    b += m;

    /* Show the differences */
    for(i=0; i<nr; i++){
      unsigned char *alignment;
      ma = R[r+i*3+1];   /* Lines on left but not on right */
      mb = R[r+i*3+2];   /* Lines on right but not on left */

      /* If the gap between the current diff and then next diff within the
         same block is not too great, then render them as if they are a
         single diff. */
      while( i<nr-1 && smallGap(&R[r+i*3]) ){
        i++;
        m = R[r+i*3];
        ma += R[r+i*3+1] + m;
        mb += R[r+i*3+2] + m;
      }

      alignment = sbsAlignment(&A[a], ma, &B[b], mb);
      if(!alignment){
        rc = FSL_RC_OOM;
        goto end;
      }
      for(j=0; !rc && ma+mb>0; j++){
        if( alignment[j]==1 ){
          /* Delete one line from the left */
          rc = sbsWriteLineno(&s, a, SBS_LNA);
          if(rc) goto end_align;
          s.iStart = 0;
          s.zStart = "<span class=\"fsl-diff-rm\">";
          s.iEnd = LENGTH(&A[a]);
          rc = sbsWriteText(&s, &A[a], SBS_TXTA);
          if(!rc) rc = sbsWriteMarker(&s, " <", "&lt;");
          if(!rc) rc = sbsWriteNewlines(&s);
          if(rc) goto end_align;
          assert( ma>0 );
          ma--;
          a++;
        }else if( alignment[j]==3 ){
          /* The left line is changed into the right line */
          rc = sbsWriteLineChange(&s, &A[a], a, &B[b], b);
          if(rc) goto end_align;
          assert( ma>0 && mb>0 );
          ma--;
          mb--;
          a++;
          b++;
        }else if( alignment[j]==2 ){
          /* Insert one line on the right */
          if( !s.escHtml ){
            rc = sbsWriteSpace(&s, s.width + 7, SBS_TXTA);
          }
          if(!rc) rc = sbsWriteMarker(&s, " > ", "&gt;");
          if(!rc) rc = sbsWriteLineno(&s, b, SBS_LNB);
          if(rc) goto end_align;
          s.iStart = 0;
          s.zStart = "<span class=\"fsl-diff-add\">";
          s.iEnd = LENGTH(&B[b]);
          rc = sbsWriteText(&s, &B[b], SBS_TXTB);
          if(rc) goto end_align;
          assert( mb>0 );
          mb--;
          b++;
        }else{
          /* Delete from the left and insert on the right */
          rc = sbsWriteLineno(&s, a, SBS_LNA);
          if(rc) goto end_align;
          s.iStart = 0;
          s.zStart = "<span class=\"fsl-diff-rm\">";
          s.iEnd = LENGTH(&A[a]);
          rc = sbsWriteText(&s, &A[a], SBS_TXTA);
          if(!rc) rc = sbsWriteMarker(&s, " | ", "|");
          if(!rc) rc = sbsWriteLineno(&s, b, SBS_LNB);
          if(rc) goto end_align;
          s.iStart = 0;
          s.zStart = "<span class=\"fsl-diff-add\">";
          s.iEnd = LENGTH(&B[b]);
          rc = sbsWriteText(&s, &B[b], SBS_TXTB);
          if(rc) goto end_align;
          ma--;
          mb--;
          a++;
          b++;
        }
      }
      end_align:
      fsl_free(alignment);
      if(rc) goto end;
      if( i<nr-1 ){
        m = R[r+i*3+3];
        for(j=0; !rc && j<m; j++){
          rc = sbsWriteLineno(&s, a+j, SBS_LNA);
          s.iStart = s.iEnd = -1;
          if(!rc) rc = sbsWriteText(&s, &A[a+j], SBS_TXTA);
          if(!rc) rc = sbsWriteMarker(&s, "   ", "");
          if(!rc) rc = sbsWriteLineno(&s, b+j, SBS_LNB);
          if(!rc) rc = sbsWriteText(&s, &B[b+j], SBS_TXTB);
        }
        if(rc) goto end;
        b += m;
        a += m;
      }
    }

    /* Show the final common area */
    assert( nr==i );
    m = R[r+nr*3];
    if( m>nContext ) m = nContext;
    for(j=0; !rc && j<m; j++){
      rc = sbsWriteLineno(&s, a+j, SBS_LNA);
      s.iStart = s.iEnd = -1;
      if(!rc) rc = sbsWriteText(&s, &A[a+j], SBS_TXTA);
      if(!rc) rc = sbsWriteMarker(&s, "   ", "");
      if(!rc) rc = sbsWriteLineno(&s, b+j, SBS_LNB);
      if(!rc) rc = sbsWriteText(&s, &B[b+j], SBS_TXTB);
    }
    if(rc) goto end;
  } /* diff triplet loop */
  assert(!rc);
  
  if( s.escHtml && (s.apCols[SBS_LNA]->used>0) ){
    rc = diff_out(pOut, "<table class=\"fsl-sbsdiff-cols\"><tr>\n", -1);
    for(i=SBS_LNA; !rc && i<=SBS_TXTB; i++){
      rc = sbsWriteColumn(pOut, s.apCols[i], i);
    }
    rc = diff_out(pOut, "</tr></table>\n", -1);
  }else if(unesc.used){
    rc = pOut->out(pOut->oState, unesc.mem, unesc.used);
  }

  end:
  for( i = 0; i < (int)(sizeof(aCols)/sizeof(aCols[0])); ++i ){
    fsl_buffer_clear(&aCols[i]);
  }
  fsl_buffer_clear(&unesc);
  return rc;
}


/** @internal

    Performs a text diff on two buffers, either streaming the
    output to the 3rd argument or returning the results as an
    array of copy/delete/insert triples via the final argument.

    ONE of the 3rd or final arguments must be set and the other
    must be NULL

    If the 3rd argument is not NULL:

    - The 4th argument is the opaque state value passed to
    the 3rd when emitting output.
    
    - contextLines specifies the number of lines of context for the
    diff. A negative contextLines value uses a default.

    - sbsWidth, if not 0, specifies a side-by-side diff width. A
    negative sbsWidth uses a default. A 0 sbsWidth indicates a
    unified-style diff output.

    If the final argument is not NULL then the result array of
    copy/delete/insert triples is assigned to *outRaw. Ownership is
    transfered to the caller, who must eventually pass it to
    fsl_free().

    Returns 0 on success, any number of other codes on error.
*/
static int fsl_diff_text_impl(
  fsl_buffer const *pA,   /* FROM file */
  fsl_buffer const *pB,   /* TO file */
  fsl_output_f out, void * outState,
  /* ReCompiled *pRe, */ /* Only output changes where this Regexp matches */
  short contextLines,
  short sbsWidth,
  int diffFlags_,    /* FSL_DIFF_* flags */
  int ** outRaw
){
  int rc;
  DContext c;
  uint64_t diffFlags = fsl_diff_flags_convert(diffFlags_)
    | DIFF_CONTEXT_EX /* to shoehorn newer 0-handling semantics into
                         older (ported-in) code. */;
  if(!pA || !pB || (out && outRaw) || (!out && !outRaw)) return FSL_RC_MISUSE;
  else if(contextLines<0) contextLines = 5;
  else if(contextLines & ~LENGTH_MASK) contextLines = (int)LENGTH_MASK;
  diffFlags |= (LENGTH_MASK & contextLines);
  /* Encode SBS width... */
  if(sbsWidth<0
     || ((DIFF_SIDEBYSIDE & diffFlags) && !sbsWidth) ) sbsWidth = 80;
  if(sbsWidth) diffFlags |= DIFF_SIDEBYSIDE;
  diffFlags |= ((int)(sbsWidth & 0xFF))<<16;
  if( diffFlags & DIFF_INVERT ){
    fsl_buffer const *pTemp = pA;
    pA = pB;
    pB = pTemp;
  }

  /* Prepare the input files */
  memset(&c, 0, sizeof(c));
  if( (diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){
    c.same_fn = same_dline_ignore_allws;
  }else{
    c.same_fn = same_dline;
  }
  rc = break_into_lines(fsl_buffer_cstr(pA), fsl_buffer_size(pA),
                        &c.nFrom, &c.aFrom, diffFlags);
  if(rc) goto end;
  rc = break_into_lines(fsl_buffer_cstr(pB), fsl_buffer_size(pB),
                        &c.nTo, &c.aTo, diffFlags);
  if(rc) goto end;

  if( c.aFrom==0 || c.aTo==0 ){
    /* Empty diff */
    goto end;
  }

  /* Compute the difference */
  rc = diff_all(&c);
  if(rc) goto end;
  if( (diffFlags & DIFF_NOTTOOBIG)!=0 ){
    int i, m, n;
    int *a = c.aEdit;
    int mx = c.nEdit;
    for(i=m=n=0; i<mx; i+=3){ m += a[i]; n += a[i+1]+a[i+2]; }
    if( !n || n>10000 ){
      rc = FSL_RC_RANGE;
      /* diff_errmsg(pOut, DIFF_TOO_MANY_CHANGES, diffFlags); */
      goto end;;
    }
  }
  if( (diffFlags & DIFF_NOOPT)==0 ){
    diff_optimize(&c);
  }

  if( out ){
    /* Compute a context or side-by-side diff */
    /* MISSING: regex support */
    DiffOutState dos = DiffOutState_empty;
    dos.out = out;
    dos.oState = outState;
    dos.ansiColor = !!(diffFlags_ & FSL_DIFF_ANSI_COLOR);
    if( diffFlags & DIFF_SIDEBYSIDE ){
      rc = sbsDiff(&c, &dos, NULL/*pRe*/, diffFlags);
    }else{
      rc = contextDiff(&c, &dos, NULL/*pRe*/, diffFlags);
    }
  }else if(outRaw){
    /* If a context diff is not requested, then return the
       array of COPY/DELETE/INSERT triples. */
    *outRaw = c.aEdit;
    c.aEdit = NULL;
  }
  end:
  fsl_free(c.aFrom);
  fsl_free(c.aTo);
  fsl_free(c.aEdit);
  return rc;
}

int fsl_diff_text_raw(fsl_buffer const *p1, fsl_buffer const *p2,
                      int diffFlags, int ** outRaw){
  return fsl_diff_text_impl(p1, p2, NULL, NULL, 0, 0, diffFlags, outRaw);
}

int fsl_diff_text(fsl_buffer const *pA, fsl_buffer const *pB,
                  fsl_output_f out, void * outState,
                  short contextLines,
                  short sbsWidth,
                  int diffFlags ){
  return fsl_diff_text_impl(pA, pB, out, outState,
                            contextLines, sbsWidth, diffFlags, NULL );
}



int fsl_diff_text_to_buffer(fsl_buffer const *pA, fsl_buffer const *pB,
                            fsl_buffer * pOut,
                            short contextLines,
                            short sbsWidth,
                            int diffFlags ){
  return (pA && pB && pOut)
    ? fsl_diff_text_impl(pA, pB, fsl_output_f_buffer, pOut,
                         contextLines, sbsWidth, diffFlags, NULL )
    : FSL_RC_MISUSE;
}

#undef TO_BE_STATIC
#undef LENGTH_MASK
#undef LENGTH_MASK_SZ
#undef LENGTH
#undef DIFF_CONTEXT_MASK
#undef DIFF_WIDTH_MASK
#undef DIFF_IGNORE_EOLWS
#undef DIFF_IGNORE_ALLWS
#undef DIFF_SIDEBYSIDE
#undef DIFF_VERBOSE
#undef DIFF_BRIEF
#undef DIFF_HTML
#undef DIFF_LINENO
#undef DIFF_NOOPT
#undef DIFF_INVERT
#undef DIFF_CONTEXT_EX
#undef DIFF_NOTTOOBIG
#undef DIFF_STRIP_EOLCR
#undef minInt
#undef SBS_LNA
#undef SBS_TXTA
#undef SBS_MKR
#undef SBS_LNB
#undef SBS_TXTB
#undef ANN_FILE_VERS
#undef ANN_FILE_ANCEST

#undef ANSI_COLOR_BLACK
#undef ANSI_COLOR_RED
#undef ANSI_COLOR_GREEN
#undef ANSI_COLOR_YELLOW
#undef ANSI_COLOR_BLUE
#undef ANSI_COLOR_MAGENTA
#undef ANSI_COLOR_CYAN
#undef ANSI_COLOR_WHITE
#undef ANSI_BG_BLACK
#undef ANSI_BG_RED
#undef ANSI_BG_GREEN
#undef ANSI_BG_YELLOW
#undef ANSI_BG_BLUE
#undef ANSI_BG_MAGENTA
#undef ANSI_BG_CYAN
#undef ANSI_BG_WHITE
#undef ANSI_RESET_COLOR
#undef ANSI_RESET_ALL
#undef ANSI_RESET
#undef ANSI_DIFF_ADD
#undef ANSI_DIFF_MOD
#undef ANSI_DIFF_RM
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/encode.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 houses some encoding/decoding API routines.
*/
#include <assert.h>

#include "fossil-scm/fossil.h"
/* Only for debugging */
#include <stdio.h>

/*
   An array for translating single base-16 characters into a value.
   Disallowed input characters have a value of 64.
*/
static const char zDecode[] = {
  64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
  64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
  64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
   0,  1,  2,  3,  4,  5,  6,  7,   8,  9, 64, 64, 64, 64, 64, 64,
  64, 10, 11, 12, 13, 14, 15, 64,  64, 64, 64, 64, 64, 64, 64, 64,
  64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
  64, 10, 11, 12, 13, 14, 15, 64,  64, 64, 64, 64, 64, 64, 64, 64,
  64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
  64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
  64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
  64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
  64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
  64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
  64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
  64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
  64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64
};

int fsl_decode16(const unsigned char *zIn, unsigned char *pOut,
                 fsl_size_t N){
  fsl_int_t i, j;
  if( (N&1)!=0 ) return FSL_RC_RANGE;
  for(i=j=0; i<(fsl_int_t)N; i += 2, j++){
    fsl_int_t v1, v2, a;
    a = zIn[i];
    if( (a & 0x80)!=0 || (v1 = zDecode[a])==64 ) return FSL_RC_RANGE;
    a = zIn[i+1];
    if( (a & 0x80)!=0 || (v2 = zDecode[a])==64 ) return FSL_RC_RANGE;
    pOut[j] = (v1<<4) + v2;
  }
  return 0;
}


bool fsl_validate16(const char *zIn, fsl_size_t nIn){
  fsl_size_t i;
  for(i=0; i<nIn; i++, zIn++){
    if( zDecode[zIn[0]&0xff]>63 ){
      return zIn[0]==0 ? true : false;
    }
  }
  return true;
}

/*
   The array used for encoding
*/                           /* 123456789 12345  */
static const char zEncode[] = "0123456789abcdef"; 

int fsl_encode16(const unsigned char *pIn, unsigned char *zOut, fsl_size_t N){
  fsl_size_t i;
  if(!pIn || !zOut) return FSL_RC_MISUSE;
  for(i=0; i<N; i++){
    *(zOut++) = zEncode[pIn[i]>>4];
    *(zOut++) = zEncode[pIn[i]&0xf];
  }
  *zOut = 0;
  return 0;
}

void fsl_canonical16(char *z, fsl_size_t n){
  while( *z && n-- ){
    *z = zEncode[zDecode[(*z)&0x7f]&0x1f];
    ++z;
  }
}

void fsl_bytes_defossilize( unsigned char * z, fsl_size_t * resultLen ){
  fsl_size_t i, j, c;
  for(i=0; (c=z[i])!=0 && c!='\\'; i++){}
  if( c==0 ) {
    if(resultLen) *resultLen = i;
    return;
  }
  for(j=i; (c=z[i])!=0; i++){
    if( c=='\\' && z[i+1] ){
      i++;
      switch( z[i] ){
        case 'n':  c = '\n';  break;
        case 's':  c = ' ';   break;
        case 't':  c = '\t';  break;
        case 'r':  c = '\r';  break;
        case 'v':  c = '\v';  break;
        case 'f':  c = '\f';  break;
        case '0':  c = 0;     break;
        case '\\': c = '\\';  break;
        default:   c = z[i];  break;
      }
    }
    z[j++] = c;
  }
  if( z[j] ) z[j] = 0;
  if(resultLen) *resultLen = j;
}

int fsl_bytes_fossilize( unsigned char const * inp,
                         fsl_int_t nIn,
                         fsl_buffer * out ){
  fsl_size_t n, i, j, c;
  unsigned char *zOut;
  int rc;
  fsl_size_t oldUsed;
  fsl_size_t inSz;
  if(!inp || !out) return FSL_RC_MISUSE;
  else if( inp && (nIn<0) ) nIn = (fsl_int_t)fsl_strlen((char const *)inp);
  out->used = 0;
  if(!nIn) return 0;
  inSz = (fsl_size_t)nIn;
  /* Figure out how much space we'll need... */
  for(i=n=0; i<inSz; ++i){
    c = inp[i];
    if( c==0 || c==' ' || c=='\n' || c=='\t' || c=='\r' || c=='\f' || c=='\v'
        || c=='\\') ++n;
  }
  /* Reserve memory... */
  n += nIn;
  oldUsed = out->used;
  rc = fsl_buffer_reserve( out, oldUsed + (fsl_size_t)(n+1));
  if(rc) return rc;
  zOut = out->mem + oldUsed;
  /* Encode it... */
  for(i=j=0; i<(fsl_size_t)nIn; i++){
    unsigned char c = (unsigned char)inp[i];
    if( c==0 ){
      zOut[j++] = '\\';
      zOut[j++] = '0';
    }else if( c=='\\' ){
      zOut[j++] = '\\';
      zOut[j++] = '\\';
    }else if( fsl_isspace(c) ){
      zOut[j++] = '\\';
      switch( c ){
        case '\n':  c = 'n'; break;
        case ' ':   c = 's'; break;
        case '\t':  c = 't'; break;
        case '\r':  c = 'r'; break;
        case '\v':  c = 'v'; break;
        case '\f':  c = 'f'; break;
      }
      zOut[j++] = c;
    }else{
      zOut[j++] = c;
    }
  }
  zOut[j] = 0;
  out->used += j;
  return 0;
}


fsl_size_t fsl_str_to_size(char const * str){
  fsl_size_t size, oldsize, c;
  if(!str) return -1;
  for(oldsize=size=0; (c = str[0])>='0' && c<='9'; str++){
    size = oldsize*10 + c - '0';
    if( size<oldsize ) return -1;
    oldsize = size;
  }
  return size;
}

fsl_int_t fsl_str_to_int(char const * str, fsl_int_t dflt){
  fsl_size_t size, oldsize
    /* We use fsl_size_t for the calculation
       so that we can detect overflow (which is undefined
       for signed types).
    */;
  char c;
  fsl_int_t mult = 1;
  fsl_int_t rc;
  if(!str) return dflt;
  else switch(*str){
    case '+': ++str; break;
    case '-': ++str; mult = -1; break;
  };
  for(oldsize=size=0; (c = str[0])>='0' && c<='9'; str++){
    size = oldsize*10 + c - '0';
    if( size<oldsize ) /* overflow */ return dflt;
    oldsize = size;
  }
  rc = (fsl_int_t)size;
  return ((fsl_size_t)rc == size)
    ? (rc * mult)
    : dflt /* result is too big */;
}

fsl_size_t fsl_htmlize_xlate(int c, char const ** xlate){
  switch( c ){
    case '<': *xlate = "&lt;"; return 4;
    case '>': *xlate = "&gt;"; return 4;
    case '&': *xlate = "&amp;";  return 5;
    case '"': *xlate = "&quot;";  return 6;
    default: *xlate = NULL; return 1;
  }
}

int fsl_htmlize(fsl_output_f out, void * oState,
                const char *zIn, fsl_int_t n){
  int rc = 0;
  int c, i, j, len;
  char const * xlate;
  if(!out || !zIn) return FSL_RC_MISUSE;
  else if( n<0 ) n = fsl_strlen(zIn);
  for(i=j=0; !rc && (i<n); ++i){
    c = zIn[i];
    len = fsl_htmlize_xlate(c, &xlate);
    if(len>1){
      if( j<i ) rc = out(oState, zIn+j, i-j);
      if(!rc) rc = out(oState, xlate, len);
      j = i+1;
    }
  }
  if( !rc && j<i ) rc = out(oState, zIn+j, i-j);
  return rc;
}


int fsl_htmlize_to_buffer(fsl_buffer *p, const char *zIn, fsl_int_t n){
  int rc = 0;
  int c;
  fsl_int_t i = 0;
  fsl_size_t count = 0;
  char const * xl = NULL;
  if(!p || !zIn) return FSL_RC_MISUSE;
  else if( n<0 ) n = fsl_strlen(zIn);
  if(0==n) return 0;
  /* Count how many bytes we need, to avoid reallocs and the
     associated error checking... */
  for( ; i<n && (c = zIn[i])!=0; ++i ){
    count += fsl_htmlize_xlate(c, &xl);
  }
  if(count){
    rc = fsl_buffer_reserve(p, p->used + count + 1);
    if(!rc){
      /* Now none of the fsl_buffer_append()s can fail. */
      rc = fsl_htmlize(fsl_output_f_buffer, p, zIn, n);
    }
  }
  return rc;
}

char *fsl_htmlize_str(const char *zIn, fsl_int_t n){
  int rc;
  fsl_buffer b = fsl_buffer_empty;
  rc = fsl_htmlize_to_buffer(&b, zIn, n);
  if(!rc){
    return (char *)b.mem /* transfer ownership */;
  }else{
    fsl_buffer_clear(&b);
    return NULL;
  }
}

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<














































































































































































































































































































































































































































































































































































Deleted src/event.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 implements technote (formerly known as event)-related
  parts of the library.
*/
#include "fossil-scm/fossil-internal.h"
#include <assert.h>

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


int fsl_event_ids_get( fsl_cx * f, fsl_list * tgt ){
  fsl_db * db = fsl_needs_repo(f);
  if(!f || !tgt) return FSL_RC_MISUSE;
  else if(!db) return FSL_RC_NOT_A_REPO;
  else {
    int rc = fsl_db_select_slist( db, tgt,
                                  "SELECT substr(tagname,7) AS n "
                                  "FROM tag "
                                  "WHERE tagname GLOB 'event-*' "
                                  "ORDER BY n");
    if(rc && db->error.code && !f->error.code){
      fsl_cx_uplift_db_error(f, db);
    }
    return rc;
  }
}


#undef MARKER
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




























































































Deleted src/ext_regexp.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
** 2012-11-13
**
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
******************************************************************************
**
** The code in this file implements a compact but reasonably
** efficient regular-expression matcher for posix extended regular
** expressions against UTF8 text.
**
** This file is an SQLite extension.  It registers a single function
** named "regexp(A,B)" where A is the regular expression and B is the
** string to be matched.  By registering this function, SQLite will also
** then implement the "B regexp A" operator.  Note that with the function
** the regular expression comes first, but with the operator it comes
** second.
**
**  The following regular expression syntax is supported:
**
**     X*      zero or more occurrences of X
**     X+      one or more occurrences of X
**     X?      zero or one occurrences of X
**     X{p,q}  between p and q occurrences of X
**     (X)     match X
**     X|Y     X or Y
**     ^X      X occurring at the beginning of the string
**     X$      X occurring at the end of the string
**     .       Match any single character
**     \c      Character c where c is one of \{}()[]|*+?.
**     \c      C-language escapes for c in afnrtv.  ex: \t or \n
**     \uXXXX  Where XXXX is exactly 4 hex digits, unicode value XXXX
**     \xXX    Where XX is exactly 2 hex digits, unicode value XX
**     [abc]   Any single character from the set abc
**     [^abc]  Any single character not in the set abc
**     [a-z]   Any single character in the range a-z
**     [^a-z]  Any single character not in the range a-z
**     \b      Word boundary
**     \w      Word character.  [A-Za-z0-9_]
**     \W      Non-word character
**     \d      Digit
**     \D      Non-digit
**     \s      Whitespace character
**     \S      Non-whitespace character
**
** A nondeterministic finite automaton (NFA) is used for matching, so the
** performance is bounded by O(N*M) where N is the size of the regular
** expression and M is the size of the input string.  The matcher never
** exhibits exponential behavior.  Note that the X{p,q} operator expands
** to p copies of X following by q-p copies of X? and that the size of the
** regular expression in the O(N*M) performance bound is computed after
** this expansion.
*/
#if defined(_MSC_VER)
#pragma warning ( disable : 4786 )
#endif
#ifndef SQLITE_CORE
#include "sqlite3ext.h"
/*declares extern the vtable of all the sqlite3 functions (for loadable modules only)*/
SQLITE_EXTENSION_INIT3
#else
#include "sqlite3.h"
#endif
#include "fossil-ext_regexp.h"
#include <string.h>
#include <stdlib.h>


/* The end-of-input character */
#define RE_EOF            0    /* End of input */

/* The NFA is implemented as sequence of opcodes taken from the following
** set.  Each opcode has a single integer argument.
*/
#define RE_OP_MATCH       1    /* Match the one character in the argument */
#define RE_OP_ANY         2    /* Match any one character.  (Implements ".") */
#define RE_OP_ANYSTAR     3    /* Special optimized version of .* */
#define RE_OP_FORK        4    /* Continue to both next and opcode at iArg */
#define RE_OP_GOTO        5    /* Jump to opcode at iArg */
#define RE_OP_ACCEPT      6    /* Halt and indicate a successful match */
#define RE_OP_CC_INC      7    /* Beginning of a [...] character class */
#define RE_OP_CC_EXC      8    /* Beginning of a [^...] character class */
#define RE_OP_CC_VALUE    9    /* Single value in a character class */
#define RE_OP_CC_RANGE   10    /* Range of values in a character class */
#define RE_OP_WORD       11    /* Perl word character [A-Za-z0-9_] */
#define RE_OP_NOTWORD    12    /* Not a perl word character */
#define RE_OP_DIGIT      13    /* digit:  [0-9] */
#define RE_OP_NOTDIGIT   14    /* Not a digit */
#define RE_OP_SPACE      15    /* space:  [ \t\n\r\v\f] */
#define RE_OP_NOTSPACE   16    /* Not a digit */
#define RE_OP_BOUNDARY   17    /* Boundary between word and non-word */

/* Each opcode is a "state" in the NFA */
typedef unsigned short sqlite3_ReStateNumber;

/* Because this is an NFA and not a DFA, multiple states can be active at
** once.  An instance of the following object records all active states in
** the NFA.  The implementation is optimized for the common case where the
** number of actives states is small.
*/
typedef struct sqlite3_ReStateSet {
  unsigned nState;            /* Number of current states */
  sqlite3_ReStateNumber *aState;      /* Current states */
} sqlite3_ReStateSet;

/* An input string read one character at a time.
*/
typedef struct sqlite3_ReInput sqlite3_ReInput;
struct sqlite3_ReInput {
  const unsigned char *z;  /* All text */
  int i;                   /* Next byte to read */
  int mx;                  /* EOF when i>=mx */
};

/* A compiled NFA (or an NFA that is in the process of being compiled) is
** an instance of the following object.
*/
/*typedef struct sqlite3_ReCompiled sqlite3_ReCompiled;*/
struct sqlite3_ReCompiled {
  sqlite3_ReInput sIn;        /* Regular expression text */
  const char *zErr;           /* Error message to return */
  char *aOp;                  /* Operators for the virtual machine */
  int *aArg;                  /* Arguments to each operator */
  unsigned (*xNextChar)(sqlite3_ReInput*);  /* Next character function */
  unsigned char zInit[12];    /* Initial text to match */
  int nInit;                  /* Number of characters in zInit */
  unsigned nState;            /* Number of entries in aOp[] and aArg[] */
  unsigned nAlloc;            /* Slots allocated for aOp[] and aArg[] */
};

/* Add a state to the given state set if it is not already there */
static void sqlite3re_add_state(sqlite3_ReStateSet *pSet, int newState){
  unsigned i;
  for(i=0; i<pSet->nState; i++) if( pSet->aState[i]==newState ) return;
  pSet->aState[pSet->nState++] = newState;
}

/* Extract the next unicode character from *pzIn and return it.  Advance
** *pzIn to the first byte past the end of the character returned.  To
** be clear:  this routine converts utf8 to unicode.  This routine is 
** optimized for the common case where the next character is a single byte.
*/
static unsigned sqlite3re_next_char(sqlite3_ReInput *p){
  unsigned c;
  if( p->i>=p->mx ) return 0;
  c = p->z[p->i++];
  if( c>=0x80 ){
    if( (c&0xe0)==0xc0 && p->i<p->mx && (p->z[p->i]&0xc0)==0x80 ){
      c = (c&0x1f)<<6 | (p->z[p->i++]&0x3f);
      if( c<0x80 ) c = 0xfffd;
    }else if( (c&0xf0)==0xe0 && p->i+1<p->mx && (p->z[p->i]&0xc0)==0x80
           && (p->z[p->i+1]&0xc0)==0x80 ){
      c = (c&0x0f)<<12 | ((p->z[p->i]&0x3f)<<6) | (p->z[p->i+1]&0x3f);
      p->i += 2;
      if( c<=0x3ff || (c>=0xd800 && c<=0xdfff) ) c = 0xfffd;
    }else if( (c&0xf8)==0xf0 && p->i+3<p->mx && (p->z[p->i]&0xc0)==0x80
           && (p->z[p->i+1]&0xc0)==0x80 && (p->z[p->i+2]&0xc0)==0x80 ){
      c = (c&0x07)<<18 | ((p->z[p->i]&0x3f)<<12) | ((p->z[p->i+1]&0x3f)<<6)
                       | (p->z[p->i+2]&0x3f);
      p->i += 3;
      if( c<=0xffff || c>0x10ffff ) c = 0xfffd;
    }else{
      c = 0xfffd;
    }
  }
  return c;
}
static unsigned sqlite3re_next_char_nocase(sqlite3_ReInput *p){
  unsigned c = sqlite3re_next_char(p);
  if( c>='A' && c<='Z' ) c += 'a' - 'A';
  return c;
}

/* Return true if c is a perl "word" character:  [A-Za-z0-9_] */
static int sqlite3re_word_char(int c){
  return (c>='0' && c<='9') || (c>='a' && c<='z')
      || (c>='A' && c<='Z') || c=='_';
}

/* Return true if c is a "digit" character:  [0-9] */
static int sqlite3re_digit_char(int c){
  return (c>='0' && c<='9');
}

/* Return true if c is a perl "space" character:  [ \t\r\n\v\f] */
static int sqlite3re_space_char(int c){
  return c==' ' || c=='\t' || c=='\n' || c=='\r' || c=='\v' || c=='\f';
}

/* Run a compiled regular expression on the zero-terminated input
** string zIn[].  Return true on a match and false if there is no match.
*/
int sqlite3re_match(sqlite3_ReCompiled *pRe, const unsigned char *zIn, int nIn){
  sqlite3_ReStateSet aStateSet[2], *pThis, *pNext;
  sqlite3_ReStateNumber aSpace[100];
  sqlite3_ReStateNumber *pToFree;
  unsigned int i = 0;
  unsigned int iSwap = 0;
  int c = RE_EOF+1;
  int cPrev = 0;
  int rc = 0;
  sqlite3_ReInput in;

  in.z = zIn;
  in.i = 0;
  in.mx = nIn>=0 ? nIn : (int)strlen((char const*)zIn);

  /* Look for the initial prefix match, if there is one. */
  if( pRe->nInit ){
    unsigned char x = pRe->zInit[0];
    while( in.i+pRe->nInit<=in.mx 
     && (zIn[in.i]!=x ||
         strncmp((const char*)zIn+in.i, (const char*)pRe->zInit, pRe->nInit)!=0)
    ){
      in.i++;
    }
    if( in.i+pRe->nInit>in.mx ) return 0;
  }

  if( pRe->nState<=(sizeof(aSpace)/(sizeof(aSpace[0])*2)) ){
    pToFree = 0;
    aStateSet[0].aState = aSpace;
  }else{
    pToFree = sqlite3_malloc( sizeof(sqlite3_ReStateNumber)*2*pRe->nState );
    if( pToFree==0 ) return -1;
    aStateSet[0].aState = pToFree;
  }
  aStateSet[1].aState = &aStateSet[0].aState[pRe->nState];
  pNext = &aStateSet[1];
  pNext->nState = 0;
  sqlite3re_add_state(pNext, 0);
  while( c!=RE_EOF && pNext->nState>0 ){
    cPrev = c;
    c = pRe->xNextChar(&in);
    pThis = pNext;
    pNext = &aStateSet[iSwap];
    iSwap = 1 - iSwap;
    pNext->nState = 0;
    for(i=0; i<pThis->nState; i++){
      int x = pThis->aState[i];
      switch( pRe->aOp[x] ){
        case RE_OP_MATCH: {
          if( pRe->aArg[x]==c ) sqlite3re_add_state(pNext, x+1);
          break;
        }
        case RE_OP_ANY: {
          sqlite3re_add_state(pNext, x+1);
          break;
        }
        case RE_OP_WORD: {
          if( sqlite3re_word_char(c) ) sqlite3re_add_state(pNext, x+1);
          break;
        }
        case RE_OP_NOTWORD: {
          if( !sqlite3re_word_char(c) ) sqlite3re_add_state(pNext, x+1);
          break;
        }
        case RE_OP_DIGIT: {
          if( sqlite3re_digit_char(c) ) sqlite3re_add_state(pNext, x+1);
          break;
        }
        case RE_OP_NOTDIGIT: {
          if( !sqlite3re_digit_char(c) ) sqlite3re_add_state(pNext, x+1);
          break;
        }
        case RE_OP_SPACE: {
          if( sqlite3re_space_char(c) ) sqlite3re_add_state(pNext, x+1);
          break;
        }
        case RE_OP_NOTSPACE: {
          if( !sqlite3re_space_char(c) ) sqlite3re_add_state(pNext, x+1);
          break;
        }
        case RE_OP_BOUNDARY: {
          if( sqlite3re_word_char(c)!=sqlite3re_word_char(cPrev) ) sqlite3re_add_state(pThis, x+1);
          break;
        }
        case RE_OP_ANYSTAR: {
          sqlite3re_add_state(pNext, x);
          sqlite3re_add_state(pThis, x+1);
          break;
        }
        case RE_OP_FORK: {
          sqlite3re_add_state(pThis, x+pRe->aArg[x]);
          sqlite3re_add_state(pThis, x+1);
          break;
        }
        case RE_OP_GOTO: {
          sqlite3re_add_state(pThis, x+pRe->aArg[x]);
          break;
        }
        case RE_OP_ACCEPT: {
          rc = 1;
          goto re_match_end;
        }
        case RE_OP_CC_INC:
        case RE_OP_CC_EXC: {
          int j = 1;
          int n = pRe->aArg[x];
          int hit = 0;
          for(j=1; j>0 && j<n; j++){
            if( pRe->aOp[x+j]==RE_OP_CC_VALUE ){
              if( pRe->aArg[x+j]==c ){
                hit = 1;
                j = -1;
              }
            }else{
              if( pRe->aArg[x+j]<=c && pRe->aArg[x+j+1]>=c ){
                hit = 1;
                j = -1;
              }else{
                j++;
              }
            }
          }
          if( pRe->aOp[x]==RE_OP_CC_EXC ) hit = !hit;
          if( hit ) sqlite3re_add_state(pNext, x+n);
          break;            
        }
      }
    }
  }
  for(i=0; i<pNext->nState; i++){
    if( pRe->aOp[pNext->aState[i]]==RE_OP_ACCEPT ){ rc = 1; break; }
  }
re_match_end:
  sqlite3_free(pToFree);
  return rc;
}

/* Resize the opcode and argument arrays for an RE under construction.
*/
static int sqlite3re_resize(sqlite3_ReCompiled *p, int N){
  char *aOp;
  int *aArg;
  aOp = sqlite3_realloc(p->aOp, N*sizeof(p->aOp[0]));
  if( aOp==0 ) return 1;
  p->aOp = aOp;
  aArg = sqlite3_realloc(p->aArg, N*sizeof(p->aArg[0]));
  if( aArg==0 ) return 1;
  p->aArg = aArg;
  p->nAlloc = N;
  return 0;
}

/* Insert a new opcode and argument into an RE under construction.  The
** insertion point is just prior to existing opcode iBefore.
*/
static int sqlite3re_insert(sqlite3_ReCompiled *p, int iBefore, int op, int arg){
  int i;
  if( p->nAlloc<=p->nState && sqlite3re_resize(p, p->nAlloc*2) ) return 0;
  for(i=p->nState; i>iBefore; i--){
    p->aOp[i] = p->aOp[i-1];
    p->aArg[i] = p->aArg[i-1];
  }
  p->nState++;
  p->aOp[iBefore] = op;
  p->aArg[iBefore] = arg;
  return iBefore;
}

/* Append a new opcode and argument to the end of the RE under construction.
*/
static int sqlite3re_append(sqlite3_ReCompiled *p, int op, int arg){
  return sqlite3re_insert(p, p->nState, op, arg);
}

/* Make a copy of N opcodes starting at iStart onto the end of the RE
** under construction.
*/
static void sqlite3re_copy(sqlite3_ReCompiled *p, int iStart, int N){
  if( p->nState+N>=p->nAlloc && sqlite3re_resize(p, p->nAlloc*2+N) ) return;
  memcpy(&p->aOp[p->nState], &p->aOp[iStart], N*sizeof(p->aOp[0]));
  memcpy(&p->aArg[p->nState], &p->aArg[iStart], N*sizeof(p->aArg[0]));
  p->nState += N;
}

/* Return true if c is a hexadecimal digit character:  [0-9a-fA-F]
** If c is a hex digit, also set *pV = (*pV)*16 + valueof(c).  If
** c is not a hex digit *pV is unchanged.
*/
static int sqlite3re_hex(int c, int *pV){
  if( c>='0' && c<='9' ){
    c -= '0';
  }else if( c>='a' && c<='f' ){
    c -= 'a' - 10;
  }else if( c>='A' && c<='F' ){
    c -= 'A' - 10;
  }else{
    return 0;
  }
  *pV = (*pV)*16 + (c & 0xff);
  return 1;
}

/* A backslash character has been seen, read the next character and
** return its interpretation.
*/
static unsigned sqlite3re_esc_char(sqlite3_ReCompiled *p){
  static const char zEsc[] = "afnrtv\\()*.+?[$^{|}]";
  static const char zTrans[] = "\a\f\n\r\t\v";
  int i, v = 0;
  char c;
  if( p->sIn.i>=p->sIn.mx ) return 0;
  c = p->sIn.z[p->sIn.i];
  if( c=='u' && p->sIn.i+4<p->sIn.mx ){
    const unsigned char *zIn = p->sIn.z + p->sIn.i;
    if( sqlite3re_hex(zIn[1],&v)
     && sqlite3re_hex(zIn[2],&v)
     && sqlite3re_hex(zIn[3],&v)
     && sqlite3re_hex(zIn[4],&v)
    ){
      p->sIn.i += 5;
      return v;
    }
  }
  if( c=='x' && p->sIn.i+2<p->sIn.mx ){
    const unsigned char *zIn = p->sIn.z + p->sIn.i;
    if( sqlite3re_hex(zIn[1],&v)
     && sqlite3re_hex(zIn[2],&v)
    ){
      p->sIn.i += 3;
      return v;
    }
  }
  for(i=0; zEsc[i] && zEsc[i]!=c; i++){}
  if( zEsc[i] ){
    if( i<6 ) c = zTrans[i];
    p->sIn.i++;
  }else{
    p->zErr = "unknown \\ escape";
  }
  return c;
}

/* Forward declaration */
static const char* sqlite3re_subcompile_string(sqlite3_ReCompiled*);

/* Peek at the next byte of input */
static unsigned char sqlite3rePeek(sqlite3_ReCompiled *p){
  return p->sIn.i<p->sIn.mx ? p->sIn.z[p->sIn.i] : 0;
}

/* Compile RE text into a sequence of opcodes.  Continue up to the
** first unmatched ")" character, then return.  If an error is found,
** return a pointer to the error message string.
*/
static const char* sqlite3re_subcompile_re(sqlite3_ReCompiled *p){
  const char *zErr;
  int iStart, iEnd, iGoto;
  iStart = p->nState;
  zErr = sqlite3re_subcompile_string(p);
  if( zErr ) return zErr;
  while( sqlite3rePeek(p)=='|' ){
    iEnd = p->nState;
    sqlite3re_insert(p, iStart, RE_OP_FORK, iEnd + 2 - iStart);
    iGoto = sqlite3re_append(p, RE_OP_GOTO, 0);
    p->sIn.i++;
    zErr = sqlite3re_subcompile_string(p);
    if( zErr ) return zErr;
    p->aArg[iGoto] = p->nState - iGoto;
  }
  return 0;
}

/* Compile an element of regular expression text (anything that can be
** an operand to the "|" operator).  Return NULL on success or a pointer
** to the error message if there is a problem.
*/
static const char* sqlite3re_subcompile_string(sqlite3_ReCompiled *p){
  int iPrev = -1;
  int iStart;
  unsigned c;
  const char *zErr;
  while( (c = p->xNextChar(&p->sIn))!=0 ){
    iStart = p->nState;
    switch( c ){
      case '|':
      case '$': 
      case ')': {
        p->sIn.i--;
        return 0;
      }
      case '(': {
        zErr = sqlite3re_subcompile_re(p);
        if( zErr ) return zErr;
        if( sqlite3rePeek(p)!=')' ) return "unmatched '('";
        p->sIn.i++;
        break;
      }
      case '.': {
        if( sqlite3rePeek(p)=='*' ){
          sqlite3re_append(p, RE_OP_ANYSTAR, 0);
          p->sIn.i++;
        }else{ 
          sqlite3re_append(p, RE_OP_ANY, 0);
        }
        break;
      }
      case '*': {
        if( iPrev<0 ) return "'*' without operand";
        sqlite3re_insert(p, iPrev, RE_OP_GOTO, p->nState - iPrev + 1);
        sqlite3re_append(p, RE_OP_FORK, iPrev - p->nState + 1);
        break;
      }
      case '+': {
        if( iPrev<0 ) return "'+' without operand";
        sqlite3re_append(p, RE_OP_FORK, iPrev - p->nState);
        break;
      }
      case '?': {
        if( iPrev<0 ) return "'?' without operand";
        sqlite3re_insert(p, iPrev, RE_OP_FORK, p->nState - iPrev+1);
        break;
      }
      case '{': {
        int m = 0, n = 0;
        int sz, j;
        if( iPrev<0 ) return "'{m,n}' without operand";
        while( (c=sqlite3rePeek(p))>='0' && c<='9' ){ m = m*10 + c - '0'; p->sIn.i++; }
        n = m;
        if( c==',' ){
          p->sIn.i++;
          n = 0;
          while( (c=sqlite3rePeek(p))>='0' && c<='9' ){ n = n*10 + c-'0'; p->sIn.i++; }
        }
        if( c!='}' ) return "unmatched '{'";
        if( n>0 && n<m ) return "n less than m in '{m,n}'";
        p->sIn.i++;
        sz = p->nState - iPrev;
        if( m==0 ){
          if( n==0 ) return "both m and n are zero in '{m,n}'";
          sqlite3re_insert(p, iPrev, RE_OP_FORK, sz+1);
          n--;
        }else{
          for(j=1; j<m; j++) sqlite3re_copy(p, iPrev, sz);
        }
        for(j=m; j<n; j++){
          sqlite3re_append(p, RE_OP_FORK, sz+1);
          sqlite3re_copy(p, iPrev, sz);
        }
        if( n==0 && m>0 ){
          sqlite3re_append(p, RE_OP_FORK, -sz);
        }
        break;
      }
      case '[': {
        int iFirst = p->nState;
        if( sqlite3rePeek(p)=='^' ){
          sqlite3re_append(p, RE_OP_CC_EXC, 0);
          p->sIn.i++;
        }else{
          sqlite3re_append(p, RE_OP_CC_INC, 0);
        }
        while( (c = p->xNextChar(&p->sIn))!=0 ){
          if( c=='[' && sqlite3rePeek(p)==':' ){
            return "POSIX character classes not supported";
          }
          if( c=='\\' ) c = sqlite3re_esc_char(p);
          if( sqlite3rePeek(p)=='-' ){
            sqlite3re_append(p, RE_OP_CC_RANGE, c);
            p->sIn.i++;
            c = p->xNextChar(&p->sIn);
            if( c=='\\' ) c = sqlite3re_esc_char(p);
            sqlite3re_append(p, RE_OP_CC_RANGE, c);
          }else{
            sqlite3re_append(p, RE_OP_CC_VALUE, c);
          }
          if( sqlite3rePeek(p)==']' ){ p->sIn.i++; break; }
        }
        if( c==0 ) return "unclosed '['";
        p->aArg[iFirst] = p->nState - iFirst;
        break;
      }
      case '\\': {
        int specialOp = 0;
        switch( sqlite3rePeek(p) ){
          case 'b': specialOp = RE_OP_BOUNDARY;   break;
          case 'd': specialOp = RE_OP_DIGIT;      break;
          case 'D': specialOp = RE_OP_NOTDIGIT;   break;
          case 's': specialOp = RE_OP_SPACE;      break;
          case 'S': specialOp = RE_OP_NOTSPACE;   break;
          case 'w': specialOp = RE_OP_WORD;       break;
          case 'W': specialOp = RE_OP_NOTWORD;    break;
        }
        if( specialOp ){
          p->sIn.i++;
          sqlite3re_append(p, specialOp, 0);
        }else{
          c = sqlite3re_esc_char(p);
          sqlite3re_append(p, RE_OP_MATCH, c);
        }
        break;
      }
      default: {
        sqlite3re_append(p, RE_OP_MATCH, c);
        break;
      }
    }
    iPrev = iStart;
  }
  return 0;
}

/* Free and reclaim all the memory used by a previously compiled
** regular expression.  Applications should invoke this routine once
** for every call to re_compile() to avoid memory leaks.
*/
void sqlite3re_free(sqlite3_ReCompiled *pRe){
  if( pRe ){
    sqlite3_free(pRe->aOp);
    sqlite3_free(pRe->aArg);
    sqlite3_free(pRe);
  }
}

/*
** Compile a textual regular expression in zIn[] into a compiled regular
** expression suitable for us by re_match() and return a pointer to the
** compiled regular expression in *ppRe.  Return NULL on success or an
** error message if something goes wrong.
*/
const char* sqlite3re_compile(sqlite3_ReCompiled **ppRe, const char *zIn, int noCase){
  sqlite3_ReCompiled *pRe;
  const char *zErr;
  int i, j;

  *ppRe = 0;
  pRe = sqlite3_malloc( sizeof(*pRe) );
  if( pRe==0 ){
    return "out of memory";
  }
  memset(pRe, 0, sizeof(*pRe));
  pRe->xNextChar = noCase ? sqlite3re_next_char_nocase : sqlite3re_next_char;
  if( sqlite3re_resize(pRe, 30) ){
    sqlite3re_free(pRe);
    return "out of memory";
  }
  if( zIn[0]=='^' ){
    zIn++;
  }else{
    sqlite3re_append(pRe, RE_OP_ANYSTAR, 0);
  }
  pRe->sIn.z = (unsigned char*)zIn;
  pRe->sIn.i = 0;
  pRe->sIn.mx = (int)strlen(zIn);
  zErr = sqlite3re_subcompile_re(pRe);
  if( zErr ){
    sqlite3re_free(pRe);
    return zErr;
  }
  if( sqlite3rePeek(pRe)=='$' && pRe->sIn.i+1>=pRe->sIn.mx ){
    sqlite3re_append(pRe, RE_OP_MATCH, RE_EOF);
    sqlite3re_append(pRe, RE_OP_ACCEPT, 0);
    *ppRe = pRe;
  }else if( pRe->sIn.i>=pRe->sIn.mx ){
    sqlite3re_append(pRe, RE_OP_ACCEPT, 0);
    *ppRe = pRe;
  }else{
    sqlite3re_free(pRe);
    return "unrecognized character";
  }

  /* The following is a performance optimization.  If the regex begins with
  ** ".*" (if the input regex lacks an initial "^") and afterwards there are
  ** one or more matching characters, enter those matching characters into
  ** zInit[].  The re_match() routine can then search ahead in the input 
  ** string looking for the initial match without having to run the whole
  ** regex engine over the string.  Do not worry able trying to match
  ** unicode characters beyond plane 0 - those are very rare and this is
  ** just an optimization. */
  if( pRe->aOp[0]==RE_OP_ANYSTAR ){
    for(j=0, i=1; j<(int)sizeof(pRe->zInit)-2 && pRe->aOp[i]==RE_OP_MATCH; i++){
      unsigned x = pRe->aArg[i];
      if( x<=127 ){
        pRe->zInit[j++] = x;
      }else if( x<=0xfff ){
        pRe->zInit[j++] = 0xc0 | (x>>6);
        pRe->zInit[j++] = 0x80 | (x&0x3f);
      }else if( x<=0xffff ){
        pRe->zInit[j++] = 0xd0 | (x>>12);
        pRe->zInit[j++] = 0x80 | ((x>>6)&0x3f);
        pRe->zInit[j++] = 0x80 | (x&0x3f);
      }else{
        break;
      }
    }
    if( j>0 && pRe->zInit[j-1]==0 ) j--;
    pRe->nInit = j;
  }
  return pRe->zErr;
}

/*
** Implementation of the regexp() SQL function.  This function implements
** the build-in REGEXP operator.  The first argument to the function is the
** pattern and the second argument is the string.  So, the SQL statements:
**
**       A REGEXP B
**
** is implemented as regexp(B,A).
*/
static void re_sql_func(
  sqlite3_context *context, 
  int argc, 
  sqlite3_value **argv
){
  sqlite3_ReCompiled *pRe;  /* Compiled regular expression */
  const char *zPattern;     /* The regular expression */
  const unsigned char *zStr;/* String being searched */
  const char *zErr;         /* Compile error message */
  int setAux = 0;           /* True to invoke sqlite3_set_auxdata() */

  pRe = sqlite3_get_auxdata(context, 0);
  if( pRe==0 ){
    zPattern = (const char*)sqlite3_value_text(argv[0]);
    if( zPattern==0 ) return;
    zErr = sqlite3re_compile(&pRe, zPattern, 0);
    if( zErr ){
      sqlite3re_free(pRe);
      sqlite3_result_error(context, zErr, -1);
      return;
    }
    if( pRe==0 ){
      sqlite3_result_error_nomem(context);
      return;
    }
    setAux = 1;
  }
  zStr = (const unsigned char*)sqlite3_value_text(argv[1]);
  if( zStr!=0 ){
    sqlite3_result_int(context, sqlite3re_match(pRe, zStr, -1));
  }
  if( setAux ){
    sqlite3_set_auxdata(context, 0, pRe, (void(*)(void*))sqlite3re_free);
  }
}

/*
** Invoke this routine to register the regexp() function with the
** SQLite database connection.
*/
int sqlite3_regexp_init(
  sqlite3 *db, 
  char **pzErrMsg, 
  const struct sqlite3_api_routines *pApi
){
  int rc = SQLITE_OK;
#ifndef SQLITE_CORE
	SQLITE_EXTENSION_INIT2(pApi)	/*sets up the 'vtable' to the host exe sqlite3 impl*/
#else
#endif
  rc = sqlite3_create_function(db, "regexp", 2, SQLITE_UTF8, 0,
                                 re_sql_func, 0, 0);
  return rc;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






















































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/forum.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 houses the code for forum-level APIS.
*/
#include <assert.h>

#include "fossil-scm/fossil-internal.h"

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

int fsl_repo_install_schema_forum(fsl_cx *f){
  int rc;
  fsl_db * db = fsl_needs_repo(f);
  if(!db) return FSL_RC_NOT_A_REPO;
  if(fsl_db_table_exists(db, FSL_DBROLE_REPO, "forumpost")){
    return 0;
  }
  MARKER(("table not exists?\n"));
  rc = fsl_db_exec_multi(db, "%s",fsl_schema_forum());
  if(rc){
    rc = fsl_cx_uplift_db_error(f, db);
  }
  return rc;
}



#undef MARKER
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


























































































Deleted src/fossil-ext_regexp.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
** 2012-11-13
**
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
******************************************************************************
**
** The code in this file implements a compact but reasonably
** efficient regular-expression matcher for posix extended regular
** expressions against UTF8 text.
**
** This file is an SQLite extension.  It registers a single function
** named "regexp(A,B)" where A is the regular expression and B is the
** string to be matched.  By registering this function, SQLite will also
** then implement the "B regexp A" operator.  Note that with the function
** the regular expression comes first, but with the operator it comes
** second.
**
**  The following regular expression syntax is supported:
**
**     X*      zero or more occurrences of X
**     X+      one or more occurrences of X
**     X?      zero or one occurrences of X
**     X{p,q}  between p and q occurrences of X
**     (X)     match X
**     X|Y     X or Y
**     ^X      X occurring at the beginning of the string
**     X$      X occurring at the end of the string
**     .       Match any single character
**     \c      Character c where c is one of \{}()[]|*+?.
**     \c      C-language escapes for c in afnrtv.  ex: \t or \n
**     \uXXXX  Where XXXX is exactly 4 hex digits, unicode value XXXX
**     \xXX    Where XX is exactly 2 hex digits, unicode value XX
**     [abc]   Any single character from the set abc
**     [^abc]  Any single character not in the set abc
**     [a-z]   Any single character in the range a-z
**     [^a-z]  Any single character not in the range a-z
**     \b      Word boundary
**     \w      Word character.  [A-Za-z0-9_]
**     \W      Non-word character
**     \d      Digit
**     \D      Non-digit
**     \s      Whitespace character
**     \S      Non-whitespace character
**
** A nondeterministic finite automaton (NFA) is used for matching, so the
** performance is bounded by O(N*M) where N is the size of the regular
** expression and M is the size of the input string.  The matcher never
** exhibits exponential behavior.  Note that the X{p,q} operator expands
** to p copies of X following by q-p copies of X? and that the size of the
** regular expression in the O(N*M) performance bound is computed after
** this expansion.
*/
#if !defined NET_FOSSIL_SCM_REGEXP_H_INCLUDED
#define NET_FOSSIL_SCM_REGEXP_H_INCLUDED

#ifdef __cplusplus
extern "C" {
#endif

struct sqlite3_api_routines;

/*(this file is derived from sqlite3 regexp.c; I wanted to expose
the engine to also be used directly*/

typedef struct sqlite3_ReCompiled sqlite3_ReCompiled;


/*
** Compile a textual regular expression in zIn[] into a compiled regular
** expression suitable for us by sqlite3re_match() and return a pointer to the
** compiled regular expression in *ppRe.  Return NULL on success or an
** error message if something goes wrong.
*/
const char* sqlite3re_compile ( sqlite3_ReCompiled** ppRe, const char* zIn, int noCase );


/* Free and reclaim all the memory used by a previously compiled
** regular expression.  Applications should invoke this routine once
** for every call to re_compile() to avoid memory leaks.
*/
void sqlite3re_free ( sqlite3_ReCompiled* pRe );


/* Run a compiled regular expression on the zero-terminated input
** string zIn[].  Return true on a match and false if there is no match.
*/
int sqlite3re_match ( sqlite3_ReCompiled* pRe, const unsigned char* zIn, int nIn );



/*registers the REGEXP extension function into a sqlite3 database instance */
int sqlite3_regexp_init ( sqlite3* db, char** pzErrMsg,
    const struct sqlite3_api_routines* pApi );


#ifdef __cplusplus
}
#endif

#else
#endif  /*#ifndef NET_FOSSIL_SCM_REGEXP_H_INCLUDED*/
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


























































































































































































































Deleted src/fs.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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).
*/
#ifdef _WIN32
# undef __STRICT_ANSI__ /* Needed for _wfopen */
#endif
#include "fossil-scm/fossil-internal.h"

#include <assert.h>
#include <string.h> /* strlen() */
#include <stddef.h> /* NULL on linux */
#include <ctype.h>
#include <errno.h>
#include <dirent.h>
#ifdef _WIN32
# define DIR _WDIR
# define dirent _wdirent
# define opendir _wopendir
# define readdir _wreaddir
# define closedir _wclosedir
# include <direct.h>
# include <windows.h>
# include <sys/utime.h>
# if !defined(ELOOP)
#  define ELOOP 114 /* Missing in MinGW */
# endif
#else
# include <unistd.h> /* access(2) */
# include <sys/types.h>
# include <sys/time.h>
#endif
#include <sys/stat.h>

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


FILE *fsl_fopen(const char *zName, const char *zMode){
  FILE *f;
  if(zName && ('-'==*zName && !zName[1])){
    f = (strchr(zMode, 'w') || strchr(zMode,'+'))
      ? stdout
      : stdin
      ;
  }else{
#ifdef _WIN32
    wchar_t *uMode = (wchar_t *)fsl_utf8_to_unicode(zMode);
    wchar_t *uName = (wchar_t *)fsl_utf8_to_filename(zName);
    f = _wfopen(uName, uMode);
    fsl_filename_free(uName);
    fsl_unicode_free(uMode);
#else
    f = fopen(zName, zMode);
#endif
  }
  return f;
}


void fsl_fclose( FILE * f ){
  if(f && (stdin!=f) && (stdout!=f) && (stderr!=f)){
    fclose(f);
  }
}

/*
   Wrapper around the access() system call.
*/
int fsl_file_access(const char *zFilename, int flags){
  /* FIXME: port in fossil(1) win32_access() */
#ifdef _WIN32
  wchar_t *zMbcs = (wchar_t *)fsl_utf8_to_filename(zFilename);
#define ACC _waccess
#else
  char *zMbcs = (char*)fsl_utf8_to_filename(zFilename);
#define ACC access
#endif
  int rc = zMbcs ? ACC(zMbcs, flags) : FSL_RC_OOM;
  if(zMbcs) fsl_filename_free(zMbcs);
  return rc;
#undef ACC
}


int fsl_getcwd(char *zBuf, fsl_size_t nBuf, fsl_size_t * outLen){
#ifdef _WIN32
  /* FIXME: port in fossil(1) win32_getcwd() */
  char *zPwdUtf8;
  fsl_size_t nPwd;
  fsl_size_t i;
  wchar_t zPwd[2000];
  if(!zBuf) return FSL_RC_MISUSE;
  else if(!nBuf) return FSL_RC_RANGE;
  /*
    https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx

    It says:

    Note File I/O functions in the Windows API convert "/" to "\" as
    part of converting the name to an NT-style name, except when using
    the "\\?\" prefix as detailed in the following sections.

    So the path-demangling bits below might do more damage they
    fix?
  */
  else if( _wgetcwd(zPwd, sizeof(zPwd)/sizeof(zPwd[0])-1)==0 ){
    /* FIXME: how to determine if FSL_RC_RANGE is a better
       return value?
    */
    return FSL_RC_IO;
  }
  zPwdUtf8 = fsl_filename_to_utf8(zPwd);
  if(!zPwdUtf8) return FSL_RC_OOM;
  nPwd = strlen(zPwdUtf8);
  if( nPwd > nBuf-1 ){
    fsl_filename_free(zPwdUtf8);
    return FSL_RC_RANGE;
  }
  for(i=0; zPwdUtf8[i]; i++) if( zPwdUtf8[i]=='\\' ) zPwdUtf8[i] = '/';
  memcpy(zBuf, zPwdUtf8, nPwd+1);
  fsl_filename_free(zPwdUtf8);
  if(outLen) *outLen = nPwd;
  return 0;
#else
  if(!zBuf) return FSL_RC_MISUSE;
  else if(!nBuf) return FSL_RC_RANGE;
  else if( getcwd(zBuf,
                  nBuf /*-1 not necessary: getcwd() NUL-terminates*/)==0 ){
    return fsl_errno_to_rc(errno, FSL_RC_IO);
  }else{
    if(outLen) *outLen = fsl_strlen(zBuf);
    return 0;
  }
#endif
}

/*
   The file status information from the most recent stat() call.
  
   Use _stati64 rather than stat on windows, in order to handle files
   larger than 2GB.
*/
#if defined(_WIN32) && (defined(__MSVCRT__) || defined(_MSC_VER))
# undef stat
# define stat _stati64
#endif
/*
   On Windows S_ISLNK always returns FALSE.
*/
#if !defined(S_ISLNK)
# define S_ISLNK(x) (0)
#endif

/* Reminder: the semantics of the 3rd parameter are
   reversed from v1's fossil_stat().
*/
int fsl_stat(const char *zFilename, fsl_fstat * fst,
             bool derefSymlinks){
  /* FIXME: port in fossil(1) win32_stat() */
  if(!zFilename) return FSL_RC_MISUSE;
  else if(!*zFilename) return FSL_RC_RANGE;
  else{
    int rc;
    struct stat buf;
#if !defined(_WIN32)
    char *zMbcs = (char *)fsl_utf8_to_filename(zFilename);
    if(!zMbcs) rc = FSL_RC_OOM;
    else{
      if( derefSymlinks ){
        rc = stat(zMbcs, &buf);
      }else{
        rc = lstat(zMbcs, &buf);
      }
    }
#else
    wchar_t *zMbcs = (wchar_t *)fsl_utf8_to_filename(zFilename);
    /*trailing pathseps are forbidden in Windows stat fxns, as per doc; sigh*/
    int nzmbcslen = wcslen ( zMbcs );
    while ( nzmbcslen > 0 && ( L'\\' == zMbcs[nzmbcslen-1] ||
        L'/' == zMbcs[nzmbcslen-1] ) ) {
      zMbcs[nzmbcslen-1] = 0;
      --nzmbcslen;
    }
    rc = zMbcs ? _wstati64(zMbcs, &buf) : FSL_RC_OOM;
#endif
    if(zMbcs) fsl_filename_free(zMbcs);
    if(fst && (0==rc)){
      *fst = fsl_fstat_empty;
      fst->ctime = (fsl_time_t)buf.st_ctime;
      fst->mtime = (fsl_time_t)buf.st_mtime;
      fst->size = (fsl_size_t)buf.st_size;
      if(S_ISDIR(buf.st_mode)) fst->type = FSL_FSTAT_TYPE_DIR;
#if !defined(_WIN32)
      else if(S_ISLNK(buf.st_mode)) fst->type = FSL_FSTAT_TYPE_LINK;
#endif
      else /* if(S_ISREG(buf.st_mode)) */{
        fst->type = FSL_FSTAT_TYPE_FILE;
#if defined(_WIN32)
#  ifndef S_IXUSR
#    define S_IXUSR  _S_IEXEC
#  endif
        if(((S_IXUSR)&buf.st_mode)!=0){
          fst->perm |= FSL_FSTAT_PERM_EXE;
        }
#else
        if( ((S_IXUSR|S_IXGRP|S_IXOTH)&buf.st_mode)!=0 ){
          fst->perm |= FSL_FSTAT_PERM_EXE;
        }
#if 0
        /* Porting artifact: something to consider... */
        else if( g.allowSymlinks && S_ISLNK(buf.st_mode) )
          return PERM_LNK;
#endif
#endif
      }
    }else if(rc){
      rc = fsl_errno_to_rc(errno, FSL_RC_IO);
    }
    return rc;
  }
}

fsl_int_t fsl_file_size(const char *zFilename){
  fsl_fstat fst;
  return ( 0 != fsl_stat(zFilename, &fst, 1) )
    ? -1
    : (fsl_int_t)fst.size;
}

/*
  The family of 'wd' functions is historical in nature and not really
  needed(???) at the library level. 'wd' == 'working directory'
  (i.e. checkout).  Ideally the library won't have to do any _direct_
  manipulation of directory trees, e.g. checkouts. That is essentially
  app-level logic, though we'll need some level of infrastructure for
  the apps to build off of.  When that comes, the "wd" family of
  functions (or something similar) might come back into play.
*/

fsl_time_t fsl_file_mtime(const char *zFilename){
  fsl_fstat fst;
  return ( 0 != fsl_stat(zFilename, &fst, 1) )
    ? -1
    : (fsl_time_t)fst.mtime;
}


bool fsl_is_file(const char *zFilename){
  fsl_fstat fst;
  return ( 0 != fsl_stat(zFilename, &fst, 1) )
    ? false
    : (FSL_FSTAT_TYPE_FILE == fst.type);
}

bool fsl_is_symlink(const char *zFilename){
  fsl_fstat fst;
  return ( 0 != fsl_stat(zFilename, &fst, 0) )
    ? false
    : (FSL_FSTAT_TYPE_LINK == fst.type);
}

/*
   Return true if zPath is an absolute pathname.  Return false
   if it is relative.
*/
bool fsl_is_absolute_path(const char *zPath){
  if( zPath && ((zPath[0]=='/')
#if defined(_WIN32) || defined(__CYGWIN__)
      || (zPath[0]=='\\')
      || (fsl_isalpha(zPath[0]) && zPath[1]==':'
          && (zPath[2]=='\\' || zPath[2]=='/'))
#endif
    )
  ){
    return 1;
  }else{
    return 0;
  }
}

bool fsl_is_simple_pathname(const char *z, bool bStrictUtf8){
  int i;
  unsigned char c = (unsigned char) z[0];
  char maskNonAscii = bStrictUtf8 ? 0x80 : 0x00;
  if( c=='/' || c==0 ) return 0;
  if( c=='.' ){ /* Common cases: ./ and ../ */
    if( z[1]=='/' || z[1]==0 ) return 0;
    if( z[1]=='.' && (z[2]=='/' || z[2]==0) ) return 0;
  }
  for(i=0; (c=(unsigned char)z[i])!=0; i++){
    if( c & maskNonAscii ){
      if( (z[++i]&0xc0)!=0x80 ){
        /* Invalid first continuation byte */
        return 0;
      }
      if( c<0xc2 ){
        /* Invalid 1-byte UTF-8 sequence, or 2-byte overlong form. */
        return 0;
      }else if( (c&0xe0)==0xe0 ){
        /* 3-byte or more */
        int unicode;
        if( c&0x10 ){
          /* Unicode characters > U+FFFF are not supported.
           * Windows XP and earlier cannot handle them.
           */
          return 0;
        }
        /* This is a 3-byte UTF-8 character */
        unicode = ((c&0x0f)<<12) + ((z[i]&0x3f)<<6) + (z[i+1]&0x3f);
        if( unicode <= 0x07ff ){
          /* overlong form */
          return 0;
        }else if( unicode>=0xe000 ){
          /* U+E000..U+FFFF */
          if( (unicode<=0xf8ff) || (unicode>=0xfffe) ){
            /* U+E000..U+F8FF are for private use.
             * U+FFFE..U+FFFF are noncharacters. */
            return 0;
          } else if( (unicode>=0xfdd0) && (unicode<=0xfdef) ){
            /* U+FDD0..U+FDEF are noncharacters. */
            return 0;
          }
        }else if( (unicode>=0xd800) && (unicode<=0xdfff) ){
          /* U+D800..U+DFFF are for surrogate pairs. */
          return 0;
        }
        if( (z[++i]&0xc0)!=0x80 ){
          /* Invalid second continuation byte */
          return 0;
        }
      }
    }else if( bStrictUtf8 && (c=='\\') ){
      return 0;
    }
    if( c=='/' ){
      if( z[i+1]=='/' ) return 0;
      if( z[i+1]=='.' ){
        if( z[i+2]=='/' || z[i+2]==0 ) return 0;
        if( z[i+2]=='.' && (z[i+3]=='/' || z[i+3]==0) ) return 0;
        if( z[i+3]=='.' ) return 0;
      }
    }
  }
  if( z[i-1]=='/' ) return 0;
  return 1;
}


/*
   If the last component of the pathname in z[0]..z[j-1] is something
   other than ".." then back it out and return true.  If the last
   component is empty or if it is ".." then return false.
*/
static bool fsl_backup_dir(const char *z, fsl_int_t *pJ){
  fsl_int_t j = *pJ;
  fsl_int_t i;
  if( !j ) return 0;
  for(i=j-1; i>0 && z[i-1]!='/'; i--){}
  if( z[i]=='.' && i==j-2 && z[i+1]=='.' ) return 0;
  *pJ = i-1;
  return 1;
}



fsl_size_t fsl_file_simplify_name(char *z, fsl_int_t n_, bool slash){
  fsl_size_t i;
  fsl_size_t n = (n_<0) ? fsl_strlen(z) : (fsl_size_t)n_;
  fsl_int_t j;
  /* On windows and cygwin convert all \ characters to / */
#if defined(_WIN32) || defined(__CYGWIN__)
  for(i=0; i<n; i++){
    if( z[i]=='\\' ) z[i] = '/';
  }
#endif
  /* Removing trailing "/" characters */
  if( !slash ){
    while( n>1 && z[n-1]=='/' ){ n--; }
  }

  /* Remove duplicate '/' characters.  Except, two // at the beginning
     of a pathname is allowed since this is important on windows. */
  for(i=j=1; i<n; i++){
    z[j++] = z[i];
    while( z[i]=='/' && i<n-1 && z[i+1]=='/' ) i++;
  }
  n = j;
  /* Skip over zero or more initial "./" sequences */
  for(i=0; i<n-1 && z[i]=='.' && z[i+1]=='/'; i+=2){}

  /* Begin copying from z[i] back to z[j]... */
  for(j=0; i<n; i++){
    if( z[i]=='/' ){
      /* Skip over internal "/." directory components */
      if( z[i+1]=='.' && (i+2==n || z[i+2]=='/') ){
        i += 1;
        continue;
      }

      /* If this is a "/.." directory component then back out the
         previous term of the directory if it is something other than ".."
         or "."
      */
      if( z[i+1]=='.' && i+2<n && z[i+2]=='.' && (i+3==n || z[i+3]=='/')
       && fsl_backup_dir(z, &j)
      ){
        i += 2;
        continue;
      }
    }
    if( j>=0 ) z[j] = z[i];
    j++;
  }
  if( j==0 ) z[j++] = '.';
  z[j] = 0;
  return (fsl_size_t)j;
}

int fsl_file_canonical_name2(const char *zRoot,
                             const char *zOrigName,
                             fsl_buffer *pOut, bool slash){
  int rc;
  if(!zOrigName || !pOut) return FSL_RC_MISUSE;
  else if( fsl_is_absolute_path(zOrigName) || (zRoot && !*zRoot)){
    rc = fsl_buffer_append( pOut, zOrigName, -1 );
#if defined(_WIN32) || defined(__CYGWIN__)
    if(!rc){
      char *zOut;
      /*
         On Windows/cygwin, normalize the drive letter to upper case.
      */
      zOut = fsl_buffer_str(pOut);
      if( fsl_islower(zOut[0]) && zOut[1]==':' ){
        zOut[0] = fsl_toupper(zOut[0]);
      }
    }
#endif
  }else if(!zRoot){
    char zPwd[2000];
    fsl_size_t nOrig = fsl_strlen(zOrigName);
    assert(nOrig < sizeof(zPwd));
    rc = fsl_getcwd(zPwd, sizeof(zPwd)-nOrig, NULL);
    if(!rc){
#if defined(_WIN32)
      /*
         On Windows, normalize the drive letter to upper case.
      */
      if( !rc && fsl_islower(zPwd[0]) && zPwd[1]==':' ){
        zPwd[0] = fsl_toupper(zPwd[0]);
      }
#endif
      rc = fsl_buffer_appendf(pOut, "%//%/", zPwd, zOrigName);
    }
  }else{
    rc = fsl_buffer_appendf(pOut, "%/%s%/", zRoot,
                            *zRoot ? "/" : "",
                            zOrigName);
  }
  if(!rc){
    fsl_size_t const newLen = fsl_file_simplify_name(fsl_buffer_str(pOut),
                                                     (int)pOut->used, slash);
    /* Reminder to self: do NOT resize pOut to the new,
       post-simplification length because pOut is almost always a
       fsl_cx::scratchpad buffer and doing so forces all sorts of
       downstream reallocs. */
    pOut->used = newLen;
  }
  return rc;
}


int fsl_file_canonical_name(const char *zOrigName,
                            fsl_buffer *pOut, bool slash){
  return fsl_file_canonical_name2(NULL, zOrigName, pOut, slash);
}

int fsl_file_dirpart(char const * zFilename,
                     fsl_int_t nLen,
                     fsl_buffer * pOut,
                     bool leaveSlash){
  if(!zFilename || !*zFilename || !pOut) return FSL_RC_MISUSE;
  else if(!nLen) return FSL_RC_RANGE;
  else{
    fsl_size_t n = (nLen>0) ? (fsl_size_t)nLen : fsl_strlen(zFilename);
    char const * z = zFilename + n;
    char doBreak = 0;
    if(!n) return FSL_RC_RANGE;
    else while( !doBreak && (--z >= zFilename) ){
      switch(*z){
#if defined(_WIN32)
        case '\\':
#endif
        case '/':
          if(!leaveSlash) --z;
          doBreak = 1;
          break;
      }
    }
    if(z<=zFilename){
      return (doBreak && leaveSlash)
        ? fsl_buffer_append(pOut, zFilename, 1)
        : fsl_buffer_append(pOut, "", 0) /* ensure a NUL terminator */;
    }else{
      return fsl_buffer_append(pOut, zFilename, z-zFilename + 1);
    }
  }

}

int fsl_find_home_dir( fsl_buffer * tgt, bool requireWriteAccess ){
  char * zHome = NULL;
  int rc = 0;
  tgt->used = 0;
#if defined(_WIN32) || defined(__CYGWIN__)
  zHome = fsl_getenv("LOCALAPPDATA");
  if( zHome==0 ){
    zHome = fsl_getenv("APPDATA");
    if( zHome==0 ){
      char *zDrive = fsl_getenv("HOMEDRIVE");
      zHome = fsl_getenv("HOMEPATH");
      if( zDrive && zHome ){
        tgt->used = 0;
        rc = fsl_buffer_appendf(tgt, "%s", zDrive);
        fsl_filename_free(zDrive);
        if(rc){
          fsl_filename_free(zHome);
          return rc;
        }
      }
    }
  }
  if(NULL==zHome){
    rc = fsl_buffer_append(tgt,
                           "Cannot locate home directory - "
                           "please set the LOCALAPPDATA or "
                           "APPDATA or HOMEPATH "
                           "environment variables.",
                           -1);
    return rc ? rc : FSL_RC_NOT_FOUND;
  }
  rc = fsl_buffer_appendf( tgt, "%/", zHome );
#else
  /* Unix... */
  zHome = fsl_getenv("HOME");
  if( zHome==0 ){
    rc = fsl_buffer_append(tgt,
                           "Cannot locate home directory - "
                           "please set the HOME environment "
                           "variable.",
                           -1);
    return rc ? rc : FSL_RC_NOT_FOUND;
  }
  rc = fsl_buffer_appendf( tgt, "%s", zHome );
#endif

  fsl_filename_free(zHome);
  if(rc) return rc;
  assert(0<tgt->used);
  zHome = fsl_buffer_str(tgt);

  if( fsl_dir_check(zHome)<1 ){
    /* assert(0==tgt->used); */
    fsl_buffer tmp = fsl_buffer_empty;
    rc = fsl_buffer_appendf(&tmp,
                            "Invalid home directory: %s",
                            zHome);
    fsl_buffer_swap_free(&tmp, tgt, -1);
    return rc ? rc : FSL_RC_TYPE;
  }

#if !(defined(_WIN32) || defined(__CYGWIN__))
  /* Not sure why, but the is-writable check is historically only done
     on Unix platforms?

     TODO: this was subsequently changed in fossil(1) to only require
     that the global db dir be writable. Port the newer logic in.
  */
  if( requireWriteAccess &&
      (0 != fsl_file_access(zHome, W_OK)) ){
    fsl_buffer tmp = fsl_buffer_empty;
    rc = fsl_buffer_appendf(&tmp,
                            "Home directory [%s] must "
                            "be writeable.",
                            zHome);
    fsl_buffer_swap_free(&tmp, tgt, -1);
    return rc ? rc : FSL_RC_ACCESS;
  }
#endif

  return rc;
}

int fsl_errno_to_rc(int errNo, int dflt){
  switch(errNo){
    /* Plese expand on this as tests/use cases call for it... */
    case EINVAL:
      return FSL_RC_MISUSE;
    case ENOMEM:
      return FSL_RC_OOM;
    case EROFS:
    case EACCES:
    case EBUSY:
    case EPERM:
    case EDQUOT:
      return FSL_RC_ACCESS;
    case EISDIR:
    case ENOTDIR:
      return FSL_RC_TYPE;
    case ENAMETOOLONG:
    case ELOOP:
      return FSL_RC_RANGE;
    case ENOENT:
      return FSL_RC_NOT_FOUND;
    case EEXIST:
    case ENOTEMPTY:
      return FSL_RC_ALREADY_EXISTS;
    case EIO:
      return FSL_RC_IO;
    default:
      return dflt;
  }
}

int fsl_file_unlink(const char *zFilename){
  int rc;
#ifdef _WIN32
  wchar_t *z = (wchar_t*)fsl_utf8_to_filename(zFilename);
  rc = _wunlink(z) ? errno : 0;
#else
  char *z = (char *)fsl_utf8_to_filename(zFilename);
  rc = unlink(zFilename) ? errno : 0;
#endif
  fsl_filename_free(z);
  return rc ? fsl_errno_to_rc(errno, FSL_RC_IO) : 0;
}

int fsl_mkdir(const char *zName, bool forceFlag){
  int rc =
    /*file_wd_dir_check(zName)*/
    fsl_dir_check(zName)
    ;
  if( rc<0 ){
    if( !forceFlag ) return FSL_RC_TYPE;
    rc = fsl_file_unlink(zName);
    if(rc) return rc;
  }else if( 0==rc ){
#if defined(_WIN32)
    typedef wchar_t char_t;
#define mkdir(F,P) _wmkdir(F)
#else
    typedef char char_t;
#endif
    char_t *zMbcs = (char_t*)fsl_utf8_to_filename(zName);
    if(!zMbcs) return FSL_RC_OOM;
    rc = mkdir(zMbcs, 0755);
    fsl_filename_free(zMbcs);
    return rc ? fsl_errno_to_rc(errno, FSL_RC_IO) : 0;
#if defined(_WIN32)
#undef mkdir
#endif
  }
  return 0;
}

int fsl_mkdir_for_file(char const *zName, bool forceFlag){
  int rc;
  fsl_buffer b = fsl_buffer_empty /* we copy zName to
                                     simplify traversal */;
  fsl_size_t n = fsl_strlen(zName);
  fsl_size_t i;
  char * zCan;
  if(n==0) return FSL_RC_RANGE;
  else if(n<2) return 0/*no dir part*/;
#if 1
  /* This variant does more work (checks dirs we know already
     exist) but transforms the path into something platform-neutral.
     If we use fsl_file_simplify_name() instead then we end up
     having to do the trailing-slash logic here.
  */
  rc = fsl_file_canonical_name(zName, &b, 1);
  if(rc) goto end;
#else
  rc = fsl_buffer_append(&b, zName, n);
  if(rc) goto end;
#endif
  zCan = fsl_buffer_str(&b);
  n = b.used;
  for( i = 1; i < n; ++i ){
    if( '/'==zCan[i] ){
      zCan[i] = 0;
#if defined(_WIN32) || defined(__CYGWIN__)
      /*
         On Windows, local path looks like: C:/develop/project/file.txt
         The if stops us from trying to create a directory of a drive letter
         C: in this example.
      */
      if( !(i==2 && zCan[1]==':') ){
#endif
        rc = fsl_dir_check(zCan);
#if 0
        if(rc<0){
          if(forceFlag) rc = fsl_file_unlink(zCan);
          else rc = FSL_RC_TYPE;
          if(rc) goto end;
        }
#endif
        /* MARKER(("dir_check rc=%d, zCan=%s\n", rc, zCan)); */
        if(0>=rc){
          rc = fsl_mkdir(zCan, forceFlag);
          /* MARKER(("mkdir(%s) rc=%s\n", zCan, fsl_rc_cstr(rc))); */
          if( 0!=rc ) goto end;

        }else{
          rc = 0;
          /* Nothing to do. */
        }
#if defined(_WIN32) || defined(__CYGWIN__)
      }
#endif
      zCan[i] = '/';
    }
  }
  end:
  fsl_buffer_clear(&b);
  return rc;
}

#if defined(_WIN32)
/* Taken verbatim from fossil(1), just renamed */
/*
** Returns non-zero if the specified name represents a real directory, i.e.
** not a junction or symbolic link.  This is important for some operations,
** e.g. removing directories via _wrmdir(), because its detection of empty
** directories will (apparently) not work right for junctions and symbolic
** links, etc.
*/
static int w32_file_is_normal_dir(wchar_t *zName){
  /*
  ** Mask off attributes, applicable to directories, that are harmless for
  ** our purposes.  This may need to be updated if other attributes should
  ** be ignored by this function.
  */
  DWORD dwAttributes = GetFileAttributesW(zName);
  if( dwAttributes==INVALID_FILE_ATTRIBUTES ) return 0;
  dwAttributes &= ~(
    FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_COMPRESSED |
    FILE_ATTRIBUTE_ENCRYPTED | FILE_ATTRIBUTE_NORMAL |
    FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
  );
  return dwAttributes==FILE_ATTRIBUTE_DIRECTORY;
}
#endif


int fsl_rmdir(const char *zFilename){
  int rc = fsl_dir_check(zFilename);
  if(rc<1) return rc ? FSL_RC_TYPE : FSL_RC_NOT_FOUND;
#ifdef _WIN32
  wchar_t *z = (wchar_t*)fsl_utf8_to_filename(zFilename);
  if(w32_file_is_normal_dir(z)){
    rc = _wunlink(z) ? errno : 0;
  }else{
    rc = ENOTDIR;
  }
#else
  char *z = (char *)fsl_utf8_to_filename(zFilename);
  rc = rmdir(zFilename) ? errno : 0;
#endif
  fsl_filename_free(z);
  if(rc){
    int const eno = errno;
    switch(eno){
      /* ENOENT normally maps to FSL_RC_NOT_FOUND,
         but in this case that's ambiguous. */
      case ENOENT: rc = FSL_RC_ACCESS; break;
      default: rc = fsl_errno_to_rc(errno, FSL_RC_IO);
        break;
    }
  }
  return rc;
}

int fsl_dir_check(const char *zFilename){
  fsl_fstat fst;
  int rc;
  if( zFilename ){
#if 1
    rc = fsl_stat(zFilename, &fst, 1);
#else
    char *zFN = fsl_strdup(zFilename);
    if(!zFN) rc = FSL_RC_OOM;
    else{
      fsl_file_simplify_name(zFN, -1, 0);
      rc = fsl_stat(zFN, &fst, 1);
      fsl_free(zFN);
    }
#endif
  }else{
    rc = -1 /*fsl_stat(zFilename, &fst, 1) historic: used static stat cache*/;
  }
  return rc ? 0 : ((FSL_FSTAT_TYPE_DIR == fst.type) ? 1 : -1);
}

int fsl_chdir(const char *zChDir){
  int rc;
#ifdef _WIN32
  wchar_t *zPath = fsl_utf8_to_filename(zChDir);
  errno = 0;
  rc = (int)!SetCurrentDirectoryW(zPath);
  fsl_filename_free(zPath);
  if(rc) rc = FSL_RC_IO;
#else
  char *zPath = fsl_utf8_to_filename(zChDir);
  errno = 0;
  rc = chdir(zPath);
  fsl_filename_free(zPath);
  if(rc) rc = fsl_errno_to_rc(errno, FSL_RC_IO);
#endif
  return rc;
}


#if 0
/*
   Same as dir_check(), but takes into account symlinks.
*/
int file_wd_dir_check(const char *zFilename){
  if(!zFilename || !*zFilename) return FSL_RC_MISUSE;
  else{
    int rc;
    fsl_fstat fst = fsl_fstat_empty;
    char *zFN = fsl_strdup(zFilename);
    if(!zFN) rc = FSL_RC_OOM;
    else{
      fsl_file_simplify_name(zFN, -1, 0);
      rc = fsl_stat(zFN, &fst, 0);
      fsl_free(zFN);
    }
    return rc ? 0 : ((FSL_FSTAT_TYPE_DIR == fst.type) ? 1 : 2);
  }
}
#endif

#if 0
/* This block requires permissions flags from v1's manifest.c. */

/*
   Return TRUE if the named file is an executable.  Return false
   for directories, devices, fifos, symlinks, etc.
*/
int fsl_wd_isexe(const char *zFilename){
  return fsl_wd_perm(zFilename)==PERM_EXE;
}

/*
   Return TRUE if the named file is a symlink and symlinks are allowed.
   Return false for all other cases.
  
   On Windows, always return False.
*/
int file_wd_islink(const char *zFilename){
  return file_wd_perm(zFilename)==PERM_LNK;
}
#endif

#if 0
/**
    Same as fsl_is_file(), but takes into account symlinks.
 */
bool fsl_wd_isfile(const char *zFilename);
bool fsl_wd_isfile(const char *zFilename){
  fsl_fstat fst;
  return ( 0 != fsl_stat(zFilename, &fst, 0) )
    ? 0
    : (FSL_FSTAT_TYPE_FILE == fst.type);
}
#endif
#if 0
/**
    Same as fsl_file_mtime(), but takes into account symlinks.
 */
fsl_time_t fsl_wd_mtime(const char *zFilename);
fsl_time_t fsl_wd_mtime(const char *zFilename){
  fsl_fstat fst;
  return ( 0 != fsl_stat(zFilename, &fst, 0) )
    ? -1
    : (fsl_time_t)fst.mtime;
}

bool fsl_wd_isfile_or_link(const char *zFilename){
  fsl_fstat fst;
  return ( 0 != fsl_stat(zFilename, &fst, 0) )
    ? 0
    : ((FSL_FSTAT_TYPE_LINK == fst.type)
       || (FSL_FSTAT_TYPE_FILE == fst.type))
    ;
}
#endif

#if 0
/**
    Same as fsl_file_size(), but takes into account symlinks.
 */
fsl_size_t fsl_wd_size(const char *zFilename);
fsl_size_t fsl_wd_size(const char *zFilename){
  fsl_fstat fst;
  return ( 0 != fsl_stat(zFilename, &fst, 0) )
    ? -1
    : fst.size;
}
#endif

/*
   Set the mtime for a file.
*/
int fsl_file_mtime_set(const char *zFilename, fsl_time_t newMTime){
  if(!zFilename || !*zFilename) return FSL_RC_MISUSE;
  else{
    int rc;
    void * zMbcs;
#if !defined(_WIN32)
    struct timeval tv[2];
    if(newMTime < 0) newMTime = (fsl_time_t)time(0);
    zMbcs = fsl_utf8_to_filename(zFilename);
    if(!zMbcs) return FSL_RC_OOM;
    memset(tv, 0, sizeof(tv[0])*2);
    tv[0].tv_sec = newMTime;
    tv[1].tv_sec = newMTime;
    rc = utimes((char const *)zMbcs, tv);
#else
    struct _utimbuf tb;
    if(newMTime < 0) newMTime = (fsl_time_t)time(0);
    zMbcs = fsl_utf8_to_filename(zFilename);
    if(!zMbcs) return FSL_RC_OOM;
    tb.actime = newMTime;
    tb.modtime = newMTime;
    rc = _wutime((wchar_t const *)zMbcs, &tb);
#endif
    fsl_filename_free(zMbcs);
    return rc ? fsl_errno_to_rc(errno, FSL_RC_IO) : 0;
  }
}



void fsl_pathfinder_clear(fsl_pathfinder * pf){
  if(pf){
    fsl_list_visit_free(&pf->ext, 1);
    fsl_list_visit_free(&pf->dirs, 1);
    fsl_buffer_clear(&pf->buf);
    *pf = fsl_pathfinder_empty;
  }
}

static int fsl_pathfinder_add(fsl_list * li, char const * str){
  char * cp = fsl_strdup(str);
  int rc;
  if(!cp) rc = FSL_RC_OOM;
  else{
    rc = fsl_list_append(li, cp);
    if(rc) fsl_free(cp);
  }
  return rc;
}

int fsl_pathfinder_dir_add(fsl_pathfinder * pf, char const * dir){
  return (pf && dir)
    ? fsl_pathfinder_add(&pf->dirs, dir)
    : FSL_RC_MISUSE;
}

int fsl_pathfinder_ext_add(fsl_pathfinder * pf, char const * ext){
  return (pf && ext)
    ? fsl_pathfinder_add(&pf->ext, ext)
    : FSL_RC_MISUSE;
}

int fsl_pathfinder_search(fsl_pathfinder * pf,
                          char const * base,
                          char const ** pOut,
                          fsl_size_t * outLen ){
  fsl_buffer * buf = pf ? &pf->buf : NULL;
  fsl_list * ext;
  fsl_list * dirs;
  int rc = 0;
  fsl_size_t d, x, nD, nX, resetLen = 0;
  fsl_size_t baseLen;
  static char const pathSep =
#if defined(_WIN32)
    '\\'
#else
    '/'
#endif
    ;
  if(!buf || !base || !*base) return FSL_RC_MISUSE;
  else if(!*base) return FSL_RC_RANGE;
  else if(0==fsl_file_access( base, 0 )){
    /* Special case: if base is found as-is, without a path search,
       use it. This is arguable behaviour, though.
    */
    if(pOut) *pOut = base;
    if(outLen) *outLen = fsl_strlen(base);
    return 0;
  }
  baseLen = fsl_strlen(base);
  ext = &pf->ext;
  dirs = &pf->dirs;
  nD = dirs->used;
  nX = ext->used;
  for( d = 0; !rc && (d < nD); ++d ){
    char const * vD = (char const *)dirs->list[d];
    /*
      Search breadth-first for a file/directory named by vD/base
    */
    buf->used = 0;
    if(vD){
      fsl_size_t const used = buf->used;
      rc = fsl_buffer_append(buf, vD, -1);
      if(rc) return rc;
      if(used != buf->used){
        /* Only append separator if vD is non-empty. */
        rc = fsl_buffer_append(buf, &pathSep, 1);
        if(rc) return rc;
      }
    }
    rc = fsl_buffer_append(buf, base, (fsl_int_t)baseLen);
    if(rc) return rc;
    if(0==fsl_file_access( (char const *)buf->mem, 0 )) goto gotone;
    resetLen = buf->used;
    for( x = 0; !rc && (x < nX); ++x ){
      char const * vX = (char const *)ext->list[x];
      if(vX){
        buf->used = resetLen;
        rc = fsl_buffer_append(buf, vX, -1);
        if(rc) return rc;
      }
      assert(buf->used < buf->capacity);
      buf->mem[buf->used] = 0;
      if(0==fsl_file_access( (char const *)buf->mem, 0 )){
        goto gotone;
      }
    }
  }

  return FSL_RC_NOT_FOUND;

  gotone:
  if(outLen) *outLen = buf->used;
  if(pOut) *pOut = (char const *)buf->mem;
  return 0;
}

char * fsl_file_without_drive_letter(char * zIn){
#ifdef _WIN32
  if( zIn && fsl_isalpha(zIn[0]) && zIn[1]==':' ) zIn += 2;
#endif
  return zIn;
}

int fsl_dir_is_empty(const char *path){
  struct dirent *ent;
  int            retval = 0;
  DIR *d = opendir(path);
  if(!d){
    return -1;
  }
  while((ent = readdir(d))) {
    const char * z = ent->d_name;
    if('.'==*z &&
       (!z[1] || ('.'==z[1] && !z[2]))){
      // Skip "." and ".." entries
      continue;
    }
    retval = 1;
    break;
  }
  closedir(d);
  return retval;
}

int fsl_file_exec_set(const char *zFilename, bool isExe){
#if FSL_PLATFORM_IS_WINDOWS
  return 0;
#else
  int rc = 0, err;
  struct stat sb;
  err = stat(zFilename, &sb);
  if(0==err){
    if(!S_ISREG(sb.st_mode)) return 0;
    else if(isExe){
      if( 0==(sb.st_mode & 0100) ){
        int const mode = (sb.st_mode & 0444)>>2
          /* This impl is from fossil, which is known to work, but...
             what is the >>2 for?*/;
        err = chmod(zFilename, (mode_t)(sb.st_mode | mode));
      }
    }else if( 0!=(sb.st_mode & 0100) ){
      err = chmod(zFilename, sb.st_mode & ~0111);
    }
  }
  if(err) rc = fsl_errno_to_rc(errno, FSL_RC_IO);
  return rc;
#endif
}

static int fsl_dircrawl_impl(fsl_buffer * dbuf, fsl_fstat * fst,
                             fsl_dircrawl_f cb, void * cbState,
                             fsl_dircrawl_state * dst,
                             unsigned int depth){
  int rc = 0;
  DIR *dir = opendir(fsl_buffer_cstr(dbuf));
  struct dirent * dent = 0;
  fsl_size_t const dPos = dbuf->used;
  if(!dir){
    return fsl_errno_to_rc(errno, FSL_RC_IO);
  }
  if(depth>20/*arbitrary limit to try to avoid stack overflow*/){
    return FSL_RC_RANGE;
  }
  while(!rc && (dent = readdir(dir))){
    const char * z = dent->d_name;
    if('.'==*z &&
       (!z[1] || ('.'==z[1] && !z[2]))){
      // Skip "." and ".." entries
      continue;
    }
    dbuf->used = dPos;
    rc = fsl_buffer_appendf(dbuf, "/%s", z);
    if(rc) break;
    fsl_size_t const newLen = dbuf->used;
    if(fsl_stat((char const *)dbuf->mem, fst, false)){
      // Simply skip stat errors. i was once bitten by an app which did
      // not do so. Scarred for life. Too soon.
      rc = 0;
      continue;
    }
    switch(fst->type){
      case FSL_FSTAT_TYPE_LINK:
      case FSL_FSTAT_TYPE_DIR:
      case FSL_FSTAT_TYPE_FILE:
        break;
      default: continue;
    }
    dbuf->mem[dbuf->used = dPos] = 0;
    dst->absoluteDir = (char const *)dbuf->mem;
    dst->entryName = z;
    dst->entryType = fst->type;
    dst->depth = depth;
    rc = cb( dst );
    if(!rc){
      dbuf->mem[dbuf->used] = '/';
      dbuf->used = newLen;
      if(FSL_FSTAT_TYPE_DIR==fst->type){
        rc = fsl_dircrawl_impl( dbuf, fst, cb, cbState, dst, depth+1 );
      }
    }
  }
  closedir(dir);
  return rc;
}

int fsl_dircrawl(char const * dirName, fsl_dircrawl_f callback,
                 void * cbState){
  fsl_buffer dbuf = fsl_buffer_empty;
  fsl_fstat fst = fsl_fstat_empty;
  int rc = fsl_file_canonical_name(dirName, &dbuf, false);
  fsl_dircrawl_state dst;
  if(!rc && '/' == dbuf.mem[dbuf.used-1]){
    dbuf.mem[--dbuf.used] = 0;
  }
  memset(&dst, 0, sizeof(dst));
  dst.callbackState = cbState;
  while(!rc){
    rc = fsl_stat((char const *)dbuf.mem, &fst, false);
    if(rc) break;
    else if(FSL_FSTAT_TYPE_DIR!=fst.type){
      rc = FSL_RC_TYPE;
      break;
    }
    rc = fsl_dircrawl_impl(&dbuf, &fst, callback, cbState, &dst, 1);
    if(FSL_RC_BREAK==rc) rc = 0;
    break;
  }
  fsl_buffer_clear(&dbuf);
  return rc;
}

bool fsl_is_file_or_link(const char *zFilename){
  fsl_fstat fst = fsl_fstat_empty;
  if(fsl_stat(zFilename, &fst, false)) return false;
  return fst.type==FSL_FSTAT_TYPE_FILE
    || fst.type==FSL_FSTAT_TYPE_LINK;
}

fsl_size_t fsl_strip_trailing_slashes(char * name, fsl_int_t nameLen){
  fsl_size_t rc = 0;
  if(nameLen < 0) nameLen = (fsl_int_t)fsl_strlen(name);
  if(nameLen){
    char * z = name + nameLen - 1;
    for( ; (z>=name) && ('/'==*z); --z){
      *z = 0;
      ++rc;
    }
  }
  return rc;
}

void fsl_buffer_strip_slashes(fsl_buffer * b){
  b->used -= fsl_strip_trailing_slashes((char *)b->mem,
                                        (fsl_int_t)b->used);
}

int fsl_file_rename(const char *zFrom, const char *zTo){
  int rc;
#if defined(_WIN32)
  /** 2021-03-24: fossil's impl of this routine has 2 additional
      params (bool isFromDir, bool isToDir), which are only used on
      Windows platforms and are only to allow for 12 bytes of edge
      case in MAX_PATH handling.  We don't need them. */
  wchar_t *zMbcsFrom = fsl_utf8_to_filename(zFrom);
  wchar_t *zMbcsTo = zMbcsFrom ? fsl_utf8_to_filename(zTo) : 0;
  rc = zMbcsTo
    ? _wrename(zMbcsFrom, zMbcsTo)
    : FSL_RC_OOM;
#else
  char *zMbcsFrom = fsl_utf8_to_filename(zFrom);
  char *zMbcsTo = zMbcsFrom ? fsl_utf8_to_filename(zTo) : 0;
  rc = zMbcsTo
    ? rename(zMbcsFrom, zMbcsTo)
    : FSL_RC_OOM;
#endif
  fsl_filename_free(zMbcsTo);
  fsl_filename_free(zMbcsFrom);
  return rc
    ? (FSL_RC_OOM==rc ? rc : fsl_errno_to_rc(errno, FSL_RC_IO))
    : 0;
}

#if 0
int fsl_file_relative_name( char const * zRoot, char const * zPath,
                            fsl_buffer * pOut, char retainSlash ){
  int rc = FSL_RC_NYI;
  char * zPath;
  fsl_size_t rootLen;
  fsl_size_t pathLen;
  if(!zPath || !*zPath || !pOut) return FSL_RC_MISUSE;

  return rc;
}
#endif


#undef MARKER
#ifdef _WIN32
#  undef DIR
#  undef dirent
#  undef opendir
#  undef readdir
#  undef closedir
#endif
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


















































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/fsl.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 houses some context-independent API routines as well as
  some of the generic helper functions and types.
*/

#include "fossil-scm/fossil-internal.h"
#include "fossil-scm/fossil-repo.h"
#include "fossil-scm/fossil-checkout.h"
#include "fossil-scm/fossil-hash.h"
#include <assert.h>
#include <stdlib.h> /* malloc() and friends, qsort() */
#include <memory.h> /* memset() */
#include <time.h> /* strftime() and gmtime() */

#if defined(_WIN32) || defined(WIN32)
# include <io.h>
#define isatty(h) _isatty(h)
#else
# include <unistd.h> /* isatty() */
#endif
/* extern int isatty(int); */

#define MARKER(pfexp)                                               \
  do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__);   \
    printf pfexp;                                                   \
  } while(0)

/*
  Please keep all fsl_XXX_empty initializers in one place (here)
  and lexically sorted.
*/
const fsl_acache fsl_acache_empty = fsl_acache_empty_m;
const fsl_branch_opt fsl_branch_opt_empty = fsl_branch_opt_empty_m;
const fsl_buffer fsl_buffer_empty = fsl_buffer_empty_m;
const fsl_card_F fsl_card_F_empty = fsl_card_F_empty_m;
const fsl_card_F_list fsl_card_F_list_empty = fsl_card_F_list_empty_m;
const fsl_card_J fsl_card_J_empty = fsl_card_J_empty_m;
const fsl_card_Q fsl_card_Q_empty = fsl_card_Q_empty_m;
const fsl_card_T fsl_card_T_empty = fsl_card_T_empty_m;
const fsl_checkin_opt fsl_checkin_opt_empty = fsl_checkin_opt_empty_m;
const fsl_ckout_manage_opt fsl_ckout_manage_opt_empty =
  fsl_ckout_manage_opt_empty_m;
const fsl_ckout_unmanage_opt fsl_ckout_unmanage_opt_empty =
  fsl_ckout_unmanage_opt_empty_m;
const fsl_ckup_opt fsl_ckup_opt_empty = fsl_ckup_opt_m;
const fsl_confirmer fsl_confirmer_empty = fsl_confirmer_empty_m;
const fsl_cx fsl_cx_empty = fsl_cx_empty_m;
const fsl_cx_config fsl_cx_config_empty = fsl_cx_config_empty_m;
const fsl_cx_init_opt fsl_cx_init_opt_default = fsl_cx_init_opt_default_m;
const fsl_cx_init_opt fsl_cx_init_opt_empty = fsl_cx_init_opt_empty_m;
const fsl_db fsl_db_empty = fsl_db_empty_m;
const fsl_deck fsl_deck_empty = fsl_deck_empty_m;
const fsl_confirm_detail fsl_confirm_detail_empty =
  fsl_confirm_detail_empty_m;
const fsl_confirm_response fsl_confirm_response_empty =
  fsl_confirm_response_empty_m;
const fsl_error fsl_error_empty = fsl_error_empty_m;
const fsl_checkin_queue_opt fsl_checkin_queue_opt_empty =
  fsl_checkin_queue_opt_empty_m;
const fsl_fstat fsl_fstat_empty = fsl_fstat_empty_m;
const fsl_list fsl_list_empty = fsl_list_empty_m;
const fsl_mcache fsl_mcache_empty = fsl_mcache_empty_m;
const fsl_outputer fsl_outputer_FILE = fsl_outputer_FILE_m;
const fsl_outputer fsl_outputer_empty = fsl_outputer_empty_m;
const fsl_pathfinder fsl_pathfinder_empty = fsl_pathfinder_empty_m;
const fsl_pq fsl_pq_empty = fsl_pq_empty_m;
const fsl_repo_create_opt fsl_repo_create_opt_empty =
  fsl_repo_create_opt_empty_m;
const fsl_repo_extract_opt fsl_repo_extract_opt_empty =
  fsl_repo_extract_opt_empty_m;
const fsl_repo_extract_state fsl_repo_extract_state_empty =
  fsl_repo_extract_state_empty_m;
const fsl_repo_open_ckout_opt fsl_repo_open_ckout_opt_empty =
  fsl_repo_open_ckout_opt_m;
const fsl_ckout_revert_opt fsl_ckout_revert_opt_empty =
  fsl_ckout_revert_opt_empty_m;
const fsl_sha1_cx fsl_sha1_cx_empty = fsl_sha1_cx_empty_m;
const fsl_state fsl_state_empty = fsl_state_empty_m;
const fsl_stmt fsl_stmt_empty = fsl_stmt_empty_m;
const fsl_timer_state fsl_timer_state_empty = fsl_timer_state_empty_m;
const fsl_xlinker fsl_xlinker_empty = fsl_xlinker_empty_m;
const fsl_xlinker_list fsl_xlinker_list_empty = fsl_xlinker_list_empty_m;
const fsl_zip_writer fsl_zip_writer_empty = fsl_zip_writer_empty_m;

const fsl_allocator fsl_allocator_stdalloc = {
fsl_realloc_f_stdalloc,
NULL
};

fsl_lib_configurable_t fsl_lib_configurable = {
  {/*allocator*/ fsl_realloc_f_stdalloc, NULL}
};

void * fsl_malloc( fsl_size_t n ){
  return n
    ? fsl_realloc(NULL, n)
    : NULL;
}

void fsl_free( void * mem ){
  if(mem) fsl_realloc(mem, 0);
}

void * fsl_realloc( void * mem, fsl_size_t n ){
#define FLCA fsl_lib_configurable.allocator
  if(!mem){
    /* malloc() */
    return n
      ? FLCA.f(FLCA.state, NULL, n)
      : NULL;
  }else if(!n){
    /* free() */
    FLCA.f(FLCA.state, mem, 0);
    return NULL;
  }else{
    /* realloc() */
    return FLCA.f(FLCA.state, mem, n);
  }
#undef FLCA
}

void * fsl_realloc_f_stdalloc(void * state, void * mem, fsl_size_t n){
  if(!mem){
    return malloc(n);
  }else if(!n){
    free(mem);
    return NULL;
  }else{
    return realloc(mem, n);
  }
}

int fsl_is_uuid(char const * str){
  fsl_size_t const len = fsl_strlen(str);
  if(FSL_STRLEN_SHA1==len){
    return fsl_validate16(str, FSL_STRLEN_SHA1) ? FSL_STRLEN_SHA1 : 0;
  }else if(FSL_STRLEN_K256==len){
    return fsl_validate16(str, FSL_STRLEN_K256) ? FSL_STRLEN_K256 : 0;
  }else{
    return 0;
  }
}
int fsl_is_uuid_len(int x){
  switch(x){
    case FSL_STRLEN_SHA1:
    case FSL_STRLEN_K256:
      return x;
    default:
      return 0;
  }
}
void fsl_error_clear( fsl_error * err ){
  if(err){
    fsl_buffer_clear(&err->msg);
    *err = fsl_error_empty;
  }
}

void fsl_error_reset( fsl_error * err ){
  if(err){
    err->code = 0;
    err->msg.used = err->msg.cursor = 0;
    if(err->msg.mem) err->msg.mem[0] = 0;
  }
}


int fsl_error_copy( fsl_error const * src, fsl_error * dest ){
  if(!src || !dest || (src==dest)) return FSL_RC_MISUSE;
  else {
    int rc = 0;
    dest->msg.used = dest->msg.cursor = 0;
    dest->code = src->code;
    if(FSL_RC_OOM!=src->code){
      rc = fsl_buffer_append( &dest->msg, src->msg.mem, src->msg.used );
    }
    return rc;
  }
}

void fsl_error_move( fsl_error * lower, fsl_error * higher ){
  fsl_error const err = *lower;
  *lower = *higher;
  lower->code = 0;
  lower->msg.used = lower->msg.cursor = 0;
  *higher = err;
}

int fsl_error_setv( fsl_error * err, int code, char const * fmt,
                    va_list args ){
  if(!err) return FSL_RC_MISUSE;
  else if(!code){ /* clear error state */
    err->code = 0;
    err->msg.used = err->msg.cursor = 0;
    if(err->msg.mem){
      err->msg.mem[0] = 0;
    }
    return 0;
  }else{
    int rc = 0;
    err->msg.used = err->msg.cursor = 0;
    err->code = code;
    if(FSL_RC_OOM!=code){
      rc = fmt
        ? fsl_buffer_appendfv(&err->msg, fmt, args)
        : fsl_buffer_append(&err->msg, fsl_rc_cstr(code), -1);
      if(rc) err->code = rc;
    }
    return rc ? rc : code;
  }
}

int fsl_error_set( fsl_error * err, int code, char const * fmt,
                   ... ){
  int rc;
  va_list args;
  va_start(args,fmt);
  rc = fsl_error_setv(err, code, fmt, args);
  va_end(args);
  return rc;
}


int fsl_error_get( fsl_error const * err, char const ** str, fsl_size_t * len ){
  if(!err) return FSL_RC_MISUSE;
  else{
    if(str) *str = err->msg.used
      ? (char const *)err->msg.mem
      : NULL;
    if(len) *len = err->msg.used;
    return err->code;
  }
}


char const * fsl_rc_cstr(int rc){
  fsl_rc_e const RC = (fsl_rc_e)rc
    /* we do this so that gcc will warn if the switch() below is
       missing any fsl_rc_e entries. */
    ;
  switch(RC){
#define STR(T) case FSL_RC_##T: return "FSL_RC_" #T
    STR(ACCESS);
    STR(ALREADY_EXISTS);
    STR(AMBIGUOUS);
    STR(BREAK);
    STR(SYNTAX);
    STR(CHECKSUM_MISMATCH);
    STR(CONFLICT);
    STR(CONSISTENCY);
    STR(DB);
    STR(DELTA_INVALID_OPERATOR);
    STR(DELTA_INVALID_SEPARATOR);
    STR(DELTA_INVALID_SIZE);
    STR(DELTA_INVALID_TERMINATOR);
    STR(end);
    STR(ERROR);
    STR(IO);
    STR(MISSING_INFO);
    STR(MISUSE);
    STR(NOOP);
    STR(NOT_A_CKOUT);
    STR(NOT_A_REPO);
    STR(NOT_FOUND);
    STR(NYI);
    STR(OK);
    STR(OOM);
    STR(PHANTOM);
    STR(RANGE);
    STR(REPO_MISMATCH);
    STR(REPO_NEEDS_REBUILD);
    STR(REPO_VERSION);
    STR(SIZE_MISMATCH);
    STR(STEP_DONE);
    STR(STEP_ERROR);
    STR(STEP_ROW);
    STR(TYPE);
    STR(UNKNOWN_RESOURCE);
    STR(UNSUPPORTED);
#undef STR
  }
  return "Unknown result code";
}

char const * fsl_library_version(){
  return FSL_LIBRARY_VERSION;
}

bool fsl_library_version_matches(char const * yourLibVersion){
  return 0 == fsl_strcmp(FSL_LIBRARY_VERSION, yourLibVersion);
}

double fsl_unix_to_julian( fsl_time_t unix_ ){
  return (unix_ * 1.0 / 86400.0 ) + 2440587.5;
}

double fsl_julian_now(){
  return fsl_unix_to_julian( time(0) );
}


int fsl_strcmp(const char *zA, const char *zB){
  if( zA==0 ) return zB ? -1 : 0;
  else if( zB==0 ) return 1;
  else{
    int a, b;
    do{
      a = *zA++;
      b = *zB++;
    }while( a==b && a!=0 );
    return ((unsigned char)a) - (unsigned char)b;
  }
}


int fsl_strcmp_cmp( void const * lhs, void const * rhs ){
  return fsl_strcmp((char const *)lhs, (char const *)rhs);
}

int fsl_strncmp(const char *zA, const char *zB, fsl_size_t nByte){
  if( !zA ) return zB ? -1 : 0;
  else if( !zB ) return +1;
  else if(!nByte) return 0;
  else{
    int a, b;
    do{
      a = *zA++;
      b = *zB++;
    }while( a==b && a!=0 && (--nByte)>0 );
    return (nByte>0) ? (((unsigned char)a) - (unsigned char)b) : 0;
  }
}

  
int fsl_uuidcmp( fsl_uuid_cstr lhs, fsl_uuid_cstr rhs ){
  if(!lhs) return rhs ? -1 : 0;
  else if(!rhs) return 1;
  else if(lhs[FSL_STRLEN_SHA1] && rhs[FSL_STRLEN_SHA1]){
    return fsl_strncmp( lhs, rhs, FSL_STRLEN_K256);
  }else if(!lhs[FSL_STRLEN_SHA1] && !rhs[FSL_STRLEN_SHA1]){
    return fsl_strncmp( lhs, rhs, FSL_STRLEN_SHA1 );
  }else{
    return fsl_strcmp(lhs, rhs);
  }      
}

int fsl_strnicmp(const char *zA, const char *zB, fsl_int_t nByte){
  if( zA==0 ){
    if( zB==0 ) return 0;
    return -1;
  }else if( zB==0 ){
    return +1;
  }
  if( nByte<0 ) nByte = (fsl_int_t)fsl_strlen(zB);
  return sqlite3_strnicmp(zA, zB, nByte);
}

int fsl_stricmp(const char *zA, const char *zB){
  if( zA==0 ) return zB ? -1 : 0;
  else if( zB==0 ) return 1;
  else{
    fsl_int_t nByte;
    int rc;
    nByte = (fsl_int_t)fsl_strlen(zB);
    rc = sqlite3_strnicmp(zA, zB, nByte);
    return ( rc==0 && zA[nByte] ) ? 1 : rc;
  }
}

int fsl_stricmp_cmp( void const * lhs, void const * rhs ){
  return fsl_stricmp((char const *)lhs, (char const *)rhs);
}

fsl_size_t fsl_strlen( char const * src ){
  fsl_size_t i = 0;
  if(src) for( ; *src; ++i, ++src ){}
  return i;
}

char * fsl_strndup( char const * src, fsl_int_t len ){
  if(!src) return NULL;
  else{
    fsl_buffer b = fsl_buffer_empty;
    if(len<0) len = (fsl_int_t)fsl_strlen(src);
    fsl_buffer_append( &b, src, len );
    return (char*)b.mem;
  }
}

char * fsl_strdup( char const * src ){
  return fsl_strndup(src, -1);
}


/*
   Return TRUE if the string begins with something that looks roughly
   like an ISO date/time string.  The SQLite date/time functions will
   have the final say-so about whether or not the date/time string is
   well-formed.
*/
char fsl_str_is_date(const char *z){
  if(!z || !*z) return 0;
  else if( !fsl_isdigit(z[0]) ) return 0;
  else if( !fsl_isdigit(z[1]) ) return 0;
  else if( !fsl_isdigit(z[2]) ) return 0;
  else if( !fsl_isdigit(z[3]) ) return 0;
  else if( z[4]!='-') return 0;
  else if( !fsl_isdigit(z[5]) ) return 0;
  else if( !fsl_isdigit(z[6]) ) return 0;
  else if( z[7]!='-') return 0;
  else if( !fsl_isdigit(z[8]) ) return 0;
  else if( !fsl_isdigit(z[9]) ) return 0;
  else return 1;
}

int fsl_str_is_date2(const char *z){
  int rc = -1;
  int pos = 0;
  if(!z || !*z) return 0;
  else if( !fsl_isdigit(z[pos++]) ) return 0;
  else if( !fsl_isdigit(z[pos++]) ) return 0;
  else if( !fsl_isdigit(z[pos++]) ) return 0;
  else if( !fsl_isdigit(z[pos++]) ) return 0;
  else if( z[pos]=='-') ++pos;
  else{
    if(fsl_isdigit(z[pos++]) && '-'==z[pos++]){
      rc = 1;
    }else{
      return 0;
    }
  }
  if( !fsl_isdigit(z[pos++]) ) return 0;
  else if( !fsl_isdigit(z[pos++]) ) return 0;
  else if( z[pos++]!='-') return 0;
  else if( !fsl_isdigit(z[pos++]) ) return 0;
  else if( !fsl_isdigit(z[pos++]) ) return 0;
  assert(10==pos || 11==pos);
  return rc;
}

bool fsl_str_bool( char const * s ){
  switch(s ? *s : 0){
    case 0: case '0':
    case 'f': case 'F': // "false"
    case 'n': case 'N': // "no"
      return false;
    case '1':
    case 't': case 'T': // "true"
    case 'y': case 'Y': // "yes"
      return true;
    default: {
      char buf[5] = {0,0,0,0,0};
      int i;
      for( i = 0; (i<5) && *s; ++i, ++s ){
        buf[i] = fsl_tolower(*s);
      }
      if(0==fsl_strncmp(buf, "off", 3)) return false;
      return true;
    }
  }
}

char * fsl_guess_user_name(){
  char const ** e;
  static char const * list[] = {
  "FOSSIL_USER",
#if defined(_WIN32)
  "USERNAME",
#else
  "USER",
  "LOGNAME",
#endif
  NULL /* sentinel */
  };
  char * rv = NULL;
  for( e = list; *e; ++e ){
    rv = fsl_getenv(*e);
    if(rv){
      /*
        Because fsl_getenv() has the odd requirement of needing
        fsl_filename_free(), and we want strings returned from this
        function to be safe for passing to fsl_free(), we have to dupe
        the string. We "could" block this off to happen only on the
        platforms for which fsl_getenv() requires an extra encoding
        step, but that would likely eventually lead to a bug.
      */
      char * kludge = fsl_strdup(rv);
      fsl_filename_free(rv);
      rv = kludge;
      break;
    }
  }
  return rv;
}

void fsl_fatal( int code, char const * fmt, ... ){
  static bool inFatal = false;
  if(inFatal){
    /* This can only happen if the fsl_appendv() bits
       call this AND trigger it via fsl_fprintf() below,
       neither of which is currently the case.
    */
    assert(!"fsl_fatal() called recursively.");
    abort();
  }else{
    va_list args;
    inFatal = true;
    fsl_fprintf(stderr, "FATAL ERROR: code=%d (%s)\n",
                code, fsl_rc_cstr(code));
    if(fmt){
      va_start(args,fmt);
      fsl_fprintfv(stderr, fmt, args);
      va_end(args);
      fwrite("\n", 1, 1, stderr);
    }
    exit(EXIT_FAILURE);
  }
}

#if 0
char * fsl_unix_to_iso8601( fsl_time_t u ){
  enum { BufSize = 20 };
  char buf[BufSize]= {0,};
  time_t const tt = (time_t)u;
  fsl_strftime( buf, BufSize, "%Y-%m-%dT%H:%M:%S", gmtime(&tt) );
  return fsl_strdup(buf);
}
#endif


char fsl_iso8601_to_julian( char const * zDate, double * out ){
  /* Adapted from this article:

     https://quasar.as.utexas.edu/BillInfo/JulianDatesG.html
  */
  char const * p = zDate;
  int y = 0, m = 0, d = 0;
  int h = 0, mi = 0, s = 0, f = 0;
  double j = 0;
  if(!zDate || !*zDate){
    return 0;
  }
#define DIG(NUM) if(!fsl_isdigit(*p)) return 0; \
  NUM=(NUM*10)+(*(p++)-'0')

  DIG(y);DIG(y);DIG(y);DIG(y);
  if('-'!=*p++) return 0;
  DIG(m);DIG(m);
  if('-'!=*p++) return 0;
  DIG(d);DIG(d);
  if('T' != *p++) return 0;
  DIG(h);DIG(h);
  if(':'!=*p++) return 0;
  DIG(mi);DIG(mi);
  if(':'!=*p++) return 0;
  DIG(s);DIG(s);
  if('.'==*p++){
    DIG(f);DIG(f);DIG(f);
  }
  if(out){
    typedef int64_t TI;
    TI A, B, C, E, F;
    if(m<3){
      --y;
      m += 12;
    }
    A = y/100;
    B = A/4;
    C = 2-A+B;
    E = (TI)(365.25*(y+4716));
    F = (TI)(30.6001*(m+1));
    j = C + d + E + F - 1524.5;
    j += ((1.0*h)/24) + ((1.0*mi)/1440) + ((1.0*s)/86400);
    if(0 != f){
      j += (1.0*f)/86400000;
    }
    *out = j;
  }
  return 1;
#undef DIG
}

fsl_time_t fsl_julian_to_unix( double JD ){
  return (fsl_time_t) ((JD - 2440587.5) * 86400);
}

char fsl_julian_to_iso8601( double J, char * out, bool addMs ){
  /* Adapted from this article:

     https://quasar.as.utexas.edu/BillInfo/JulianDatesG.html
  */
  typedef int64_t TI;
  int Y, M, D, H, MI, S, F;
  TI ms;
  char * z = out;
  if(!out || (J<=0)) return 0;
  else{
    double Z;
    TI W, X;
    TI A, B;
    TI C, DD, E, F;

    Z = J + 0.5;
    W = (TI)((Z-1867216.25)/36524.25);
    X = W/4;
    A = (TI)(Z+1+W-X);
    B = A+1524;
    C = (TI)((B-122.1)/365.25);
    DD = (TI)(365.25 * C);
    E = (TI)((B-DD)/30.6001);
    F = (TI)(30.6001 * E);
    D = (int)(B - DD - F);
    M = (E<=13) ? (E-1) : (E-13);
    Y = (M<3) ? (C-4715) : (C-4716);
  }

  if(Y<0 || Y>9999) return 0;
  else if(M<1 || M>12) return 0;
  else if(D<1 || D>31) return 0;

  ms = (TI)((J-(TI)J) * 86400001.0)
    /* number of milliseconds in the fraction part of the JDay. The
       non-0 at the end works around a problem where SS.000 converts
       to (SS-1).999. This will only hide the bug for the cases i've
       seen it, and might introduce other inaccuracies
       elsewhere. Testing it against the current libfossil event table
       produces good results - at most a 1ms round-trip fidelity loss
       for the (currently ~1157) records being checked. The suffix of
       1.0 was found to be a decent value via much testing with the
       libfossil and fossil(1) source repos.
    */;

  if( (H = ms / 3600000) ){
    ms -= H * 3600000;
    H = (H + 12) % 24;
  }else{
    H = 12 /* astronomers start their day at noon. */;
  }
  if( (MI = ms / 60000) ) ms -= MI * 60000;
  if( (S = ms / 1000) ) ms -= S * 1000;
  assert(ms<1000);
  F = (int)(ms);

  assert(H>=0 && H<24);
  assert(MI>=0 && MI<60);
  assert(S>=0 && S<60);
  assert(F>=0 && F<1000);

  if(H<0 || H>23) return 0;
  else if(MI<0 || MI>59) return 0;
  else if(S<0 || S>59) return 0;
  else if(F<0 || F>999) return 0;
#define UGLY_999_KLUDGE 1
  /* The fossil(1) repo has 27 of 10041 records which exhibit the
     SS.999 behaviour commented on above. With this kludge, that
     number drops to 0. But it's still an ugly, ugly kludge.
     OTOH, the chance of the .999 being correct is 1 in 1000,
     whereas we see "correct" behaviour more often (2.7 in 1000)
     with this workaround.
   */
#if UGLY_999_KLUDGE
  if(999==F){
    char oflow = 0;
    int s2 = S, mi2 = MI, h2 = H;
    if(++s2 == 60){ /* Overflow minute */
      s2 = 0;
      if(++mi2 == 60){ /* Overflow hour */
        mi2 = 0;
        if(++h2 == 24){ /* Overflow day */
          /* leave this corner-corner case in place */
          oflow = 1;
        }
      }
    }
    /* MARKER(("UGLY 999 KLUDGE (A): H=%d MI=%d S=%d F=%d\n", H, MI, S, F)); */
    if(!oflow){
      F = 0;
      S = s2;
      MI = mi2;
      H = h2;
      /* MARKER(("UGLY 999 KLUDGE (B): H=%d MI=%d S=%d F=%d\n", H, MI, S, F)); */
    }
  }
#endif
#undef UGLY_999_KLUDGE
  *(z++) = '0'+(Y/1000);
  *(z++) = '0'+(Y%1000/100);
  *(z++) = '0'+(Y%100/10);
  *(z++) = '0'+(Y%10);
  *(z++) = '-';
  *(z++) = '0'+(M/10);
  *(z++) = '0'+(M%10);
  *(z++) = '-';
  *(z++) = '0'+(D/10);
  *(z++) = '0'+(D%10);
  *(z++) = 'T';
  *(z++) = '0'+(H/10);
  *(z++) = '0'+(H%10);
  *(z++) = ':';
  *(z++) = '0'+(MI/10);
  *(z++) = '0'+(MI%10);
  *(z++) = ':';
  *(z++) = '0'+(S/10);
  *(z++) = '0'+(S%10);
  if(addMs){
    *(z++) = '.';
    *(z++) = '0'+(F%1000/100);
    *(z++) = '0'+(F%100/10);
    *(z++) = '0'+(F%10);
  }
  *z = 0;
  return 1;
}

#if FSL_CONFIG_ENABLE_TIMER
/**
   For the fsl_timer_xxx() family of functions...
*/
#ifdef _WIN32
# include <windows.h>
#else
# include <sys/time.h>
# include <sys/resource.h>
# include <unistd.h>
# include <fcntl.h>
# include <errno.h>
#endif
#endif
/* FSL_CONFIG_ENABLE_TIMER */

/**
   Get user and kernel times in microseconds.
*/
static void fsl_cpu_times(uint64_t *piUser, uint64_t *piKernel){
#if !FSL_CONFIG_ENABLE_TIMER
  if(piUser) *piUser = 0U;
  if(piKernel) *piKernel = 0U;
#else
#ifdef _WIN32
  FILETIME not_used;
  FILETIME kernel_time;
  FILETIME user_time;
  GetProcessTimes(GetCurrentProcess(), &not_used, &not_used,
                  &kernel_time, &user_time);
  if( piUser ){
     *piUser = ((((uint64_t)user_time.dwHighDateTime)<<32) +
                         (uint64_t)user_time.dwLowDateTime + 5)/10;
  }
  if( piKernel ){
     *piKernel = ((((uint64_t)kernel_time.dwHighDateTime)<<32) +
                         (uint64_t)kernel_time.dwLowDateTime + 5)/10;
  }
#else
  struct rusage s;
  getrusage(RUSAGE_SELF, &s);
  if( piUser ){
    *piUser = ((uint64_t)s.ru_utime.tv_sec)*1000000 + s.ru_utime.tv_usec;
  }
  if( piKernel ){
    *piKernel =
              ((uint64_t)s.ru_stime.tv_sec)*1000000 + s.ru_stime.tv_usec;
  }
#endif
#endif
/* FSL_CONFIG_ENABLE_TIMER */
}


void fsl_timer_start(fsl_timer_state * ft){
  fsl_cpu_times( &ft->user, &ft->system );
}

uint64_t fsl_timer_fetch(fsl_timer_state const * t){
  uint64_t eu = 0, es = 0;
  fsl_cpu_times( &eu, &es );
  return (eu - t->user) + (es - t->system);
}

uint64_t fsl_timer_reset(fsl_timer_state * t){
  uint64_t const rc = fsl_timer_fetch(t);
  fsl_cpu_times( &t->user, &t->system );
  return rc;
}

uint64_t fsl_timer_stop(fsl_timer_state *t){
  uint64_t const rc = fsl_timer_fetch(t);
  *t = fsl_timer_state_empty;
  return rc;
}

unsigned int fsl_rgb_encode( int r, int g, int b ){
  return (unsigned int)(((r&0xFF)<<16) + ((g&0xFF)<<8) + (b&0xFF));
}

void fsl_rgb_decode( unsigned int src, int *r, int *g, int *b ){
  if(r) *r = (src&0xFF0000)>>16;
  if(g) *g = (src&0xFF00)>>8;
  if(b) *b = src&0xFF;
}

unsigned fsl_gradient_color(unsigned c1, unsigned c2, unsigned int n, unsigned int i){
  unsigned c;   /* Result color */
  unsigned x1, x2;
  if( i==0 || n==0 ) return c1;
  else if(i>=n) return c2;
  x1 = (c1>>16)&0xff;
  x2 = (c2>>16)&0xff;
  c = (x1*(n-i) + x2*i)/n<<16 & 0xff0000;
  x1 = (c1>>8)&0xff;
  x2 = (c2>>8)&0xff;
  c |= (x1*(n-i) + x2*i)/n<<8 & 0xff00;
  x1 = c1&0xff;
  x2 = c2&0xff;
  c |= (x1*(n-i) + x2*i)/n & 0xff;
  return c;
}


fsl_size_t fsl_simplify_sql( char * sql, fsl_int_t len ){
  char * wat = sql /* write pos */;
  char * rat = sql /* read pos */;
  char const * end /* one-past-the-end */;
  char inStr = 0 /* in an SQL string? */;
  char prev = 0 /* previous character. Sometimes. */;
  if(!sql || !*sql) return 0;
  else if(len < 0) len = fsl_strlen(sql);
  if(!len) return 0;
  end = sql + len;
  while( *rat && (rat < end) ){
    switch(*rat){
      case 0: break;
      case '\r':
      case '\n':
        /* Bug: we don't handle \r\n pairs. Because nobody
           should never have to :/. */
        if(inStr || (prev!=*rat)){
          /* Keep them as-is */
          prev = *wat++ = *rat++;
        }else{
          /* Collapse multiples into one. */
          ++rat;
        }
        continue;
      case ' ':
      case '\t':
      case '\v':
      case '\f':
        if(inStr){
          /* Keep them as-is */
          prev = *wat++ = *rat++;
        }else{
          /* Reduce to a single space. */
          /* f_out("prev=[%c] rat=[%c]\n", prev, *rat); */
          if(prev != *rat){
            *wat++ = ' ';
            prev = *rat;
          }
          ++rat;
        }
        continue;
      case '\'': /* SQL strings */
        prev = *wat++ = *rat++;
        if(!inStr){
          inStr = 1;
        }else if('\'' == *rat){
          /* Escaped quote */
          *wat++ = *rat++;
        }else{
          /* End of '...' string. */
          inStr = 0;
        }
        continue;
      default:
        prev = *wat++ = *rat++;
        continue;
    }
  }
  *wat = 0;
  return (fsl_size_t)(wat - sql);
}

/**
   Convenience form of fsl_simplify_sql() which assumes b holds an SQL
   string. It gets processed by fsl_simplify_sql() and its 'used'
   length potentially gets adjusted to match the adjusted SQL string.
*/
fsl_size_t fsl_simplify_sql_buffer( fsl_buffer * b ){
  return b->used = fsl_simplify_sql( (char *)b->mem, (fsl_int_t)b->used );
}

char const *fsl_preferred_ckout_db_name(){
#if FSL_PLATFORM_IS_WINDOWS
  return "_FOSSIL_";
#else
  return ".fslckout";
#endif
}

char fsl_isatty(int fd){
  return isatty(fd) ? 1 : 0;
}

bool fsl_is_reserved_fn_windows(const char *zPath, fsl_int_t nameLen){
  static const char *const azRes[] = {
    "CON", "PRN", "AUX", "NUL", "COM", "LPT"
  };
  unsigned int i;
  char const * zEnd;
  if(nameLen<0) nameLen = (fsl_int_t)fsl_strlen(zPath);
  zEnd = zPath + nameLen;
  while( zPath < zEnd ){
    for(i=0; i<sizeof(azRes)/sizeof(azRes[0]); ++i){
      if( fsl_strnicmp(zPath, azRes[i], 3)==0
       && ((i>=4 && fsl_isdigit(zPath[3])
                 && (zPath[4]=='/' || zPath[4]=='.' || zPath[4]==0))
          || (i<4 && (zPath[3]=='/' || zPath[3]=='.' || zPath[3]==0)))
      ){
        return true;
      }
    }
    while( zPath<zEnd && zPath[0]!='/' ) ++zPath;
    while( zPath<zEnd && zPath[0]=='/' ) ++zPath;
  }
  return false;
}

bool fsl_is_reserved_fn(const char *zFilename, fsl_int_t nameLen){
  fsl_size_t nFilename = nameLen>=0
    ? (fsl_size_t)nameLen : fsl_strlen(zFilename);
  char const * zEnd;
  int gotSuffix = 0;
  assert( zFilename && "API misuse" );
#if FSL_PLATFORM_IS_WINDOWS // || 1
  if(nFilename>2 && fsl_is_reserved_fn_windows(zFilename, nameLen)){
    return true;
  }
#endif
  if( nFilename<8 ) return false; /* strlen("_FOSSIL_") */
  zEnd = zFilename + nFilename;
  if( nFilename>=12 ){ /* strlen("_FOSSIL_-(shm|wal)") */
    /* Check for (-wal, -shm, -journal) suffixes, with an eye towards
    ** runtime speed. */
    if( zEnd[-4]=='-' ){
      if( fsl_strnicmp("wal", &zEnd[-3], 3)
       && fsl_strnicmp("shm", &zEnd[-3], 3) ){
        return false;
      }
      gotSuffix = 4;
    }else if( nFilename>=16 && zEnd[-8]=='-' ){ /*strlen(_FOSSIL_-journal) */
      if( fsl_strnicmp("journal", &zEnd[-7], 7) ) return false;
      gotSuffix = 8;
    }
    if( gotSuffix ){
      assert( 4==gotSuffix || 8==gotSuffix );
      zEnd -= gotSuffix;
      nFilename -= gotSuffix;
      gotSuffix = 1;
    }
    assert( nFilename>=8 && "strlen(_FOSSIL_)" );
    assert( gotSuffix==0 || gotSuffix==1 );
  }
  switch( zEnd[-1] ){
    case '_':{
      if( fsl_strnicmp("_FOSSIL_", &zEnd[-8], 8) ) return false;
      if( 8==nFilename ) return true;
      return zEnd[-9]=='/' ? true : !!gotSuffix;
    }
    case 'T':
    case 't':{
      if( nFilename<9 || zEnd[-9]!='.'
       || fsl_strnicmp(".fslckout", &zEnd[-9], 9) ){
        return false;
      }
      if( 9==nFilename ) return true;
      return zEnd[-10]=='/' ? true : !!gotSuffix;
    }
    default:{
      return false;
    }
  }
}

#undef MARKER
#if defined(_WIN32) || defined(WIN32)
#undef isatty
#endif
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<












































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/glob.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 contains some of the APIs dealing with globs.
*/
#include "fossil-scm/fossil-internal.h"
#include <assert.h>

bool fsl_str_glob(const char *zGlob, const char *z){
#if 1
  return (zGlob && z)
    ? (sqlite3_strglob(zGlob,z) ? 0 : 1)
    : 0;
#else
  int c, c2;
  int invert;
  int seen;
  while( (c = (*(zGlob++)))!=0 ){
    if( c=='*' ){
      while( (c=(*(zGlob++))) == '*' || c=='?' ){
        if( c=='?' && (*(z++))==0 ) return 0;
      }
      if( c==0 ){
        return 1;
      }else if( c=='[' ){
        while( *z && fsl_str_glob(zGlob-1,z)==0 ){
          z++;
        }
        return (*z)!=0;
      }
      while( (c2 = (*(z++)))!=0 ){
        while( c2!=c ){
          c2 = *(z++);
          if( c2==0 ) return 0;
        }
        if( fsl_str_glob(zGlob,z) ) return 1;
      }
      return 0;
    }else if( c=='?' ){
      if( (*(z++))==0 ) return 0;
    }else if( c=='[' ){
      int prior_c = 0;
      seen = 0;
      invert = 0;
      c = *(z++);
      if( c==0 ) return 0;
      c2 = *(zGlob++);
      if( c2=='^' ){
        invert = 1;
        c2 = *(zGlob++);
      }
      if( c2==']' ){
        if( c==']' ) seen = 1;
        c2 = *(zGlob++);
      }
      while( c2 && c2!=']' ){
        if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
          c2 = *(zGlob++);
          if( c>=prior_c && c<=c2 ) seen = 1;
          prior_c = 0;
        }else{
          if( c==c2 ){
            seen = 1;
          }
          prior_c = c2;
        }
        c2 = *(zGlob++);
      }
      if( c2==0 || (seen ^ invert)==0 ) return 0;
    }else{
      if( c!=(*(z++)) ) return 0;
    }
  }
  return *z==0;
#endif
}


int fsl_glob_list_parse( fsl_list * tgt, char const * zPatternList ){
  fsl_size_t i;             /* Loop counters */
  char const *z = zPatternList;
  char * cp;
  char delimiter;    /* '\'' or '\"' or 0 */
  int rc = 0;
  char const * end;
  if( !tgt || !zPatternList ) return FSL_RC_MISUSE;
  else if(!*zPatternList) return 0;
  end = zPatternList + fsl_strlen(zPatternList);
  while( (z<end) && z[0] ){
    while( fsl_isspace(z[0]) || z[0]==',' ){
      ++z;  /* Skip leading commas, spaces, and newlines */
    }
    if( z[0]==0 ) break;
    if( z[0]=='\'' || z[0]=='"' ){
      delimiter = z[0];
      ++z;
    }else{
      delimiter = ',';
    }
    /* Find the next delimter (or the end of the string). */
    for(i=0; z[i] && z[i]!=delimiter; i++){
      if( delimiter!=',' ) continue; /* If quoted, keep going. */
      if( fsl_isspace(z[i]) ) break; /* If space, stop. */
    }
    if( !i ) break;
    cp = fsl_strndup(z, (fsl_int_t)i);
    if(!cp) return FSL_RC_OOM;
    else{
      rc = fsl_list_append(tgt, cp);
      if(rc){
        fsl_free(cp);
        break;
      }
      cp[i]=0;
    }
    z += i+1;
  }
  return rc;
}


char const * fsl_glob_list_matches( fsl_list const * globList, char const * zHaystack ){
  if(!globList || !zHaystack || !*zHaystack || !globList->used) return NULL;
  else{
    char const * glob;
    fsl_size_t i = 0;
    for( ; i < globList->used; ++i){
      glob = (char const *)globList->list[i];
      if( fsl_str_glob( glob, zHaystack ) ) return glob;
    }
    return NULL;
  }
}

int fsl_glob_list_append( fsl_list * tgt, char const * zGlob ){
  if(!tgt || !zGlob || !*zGlob) return FSL_RC_MISUSE;
  else{
    char * cp = fsl_strdup(zGlob);
    int rc = cp ? 0 : FSL_RC_OOM;
    if(!rc){
      rc = fsl_list_append(tgt, cp);
      if(rc) fsl_free(cp);
    }
    return rc;
  }
}


void fsl_glob_list_clear( fsl_list * globList ){
  if(globList) fsl_list_visit_free(globList, 1);
}

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




































































































































































































































































































































Deleted src/io.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 implements the generic i/o-related parts of the library.
*/
#include "fossil-scm/fossil-internal.h"
#include <assert.h>
#include <errno.h>
#include <string.h> /* memcmp() */

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

/**
    fsl_appendf_f() impl which sends its output to fsl_output(). state
    must be a (fsl_cx*).
 */
static fsl_int_t fsl_appendf_f_fsl_output( void * state, char const * s,
                                           fsl_int_t n ){
  return fsl_output( (fsl_cx *)state, s, (fsl_size_t)n )
    ? -1
    : n;
}

int fsl_outputfv( fsl_cx * f, char const * fmt, va_list args ){
  if(!f || !fmt) return FSL_RC_MISUSE;
  else if(!*fmt) return FSL_RC_RANGE;
  else{
    long const prc = fsl_appendfv( fsl_appendf_f_fsl_output,
                                   f, fmt, args );
    return (prc>=0) ? 0 : FSL_RC_IO;
  }
}

    
int fsl_outputf( fsl_cx * f, char const * fmt, ... ){
  if(!f || !fmt) return FSL_RC_MISUSE;
  else if(!*fmt) return FSL_RC_RANGE;
  else{
    int rc;
    va_list args;
    va_start(args,fmt);
    rc = fsl_outputfv( f, fmt, args );
    va_end(args);
    return rc;
  }
}


int fsl_output( fsl_cx * cx, void const * src, fsl_size_t n ){
  if(!cx || !src) return FSL_RC_MISUSE;
  else if(!n || !cx->output.out) return 0;
  else return cx->output.out( cx->output.state.state,
                              src, n );
}

int fsl_flush( fsl_cx * f ){
  return f
    ? (f->output.flush
       ? f->output.flush(f->output.state.state)
       : 0)
    : FSL_RC_MISUSE;
}


int fsl_flush_f_FILE(void * _FILE){
  return _FILE
    ? (fflush((FILE*)_FILE) ? fsl_errno_to_rc(errno, FSL_RC_IO) : 0)
    : FSL_RC_MISUSE;
}

int fsl_output_f_FILE( void * state,
                       void const * src, fsl_size_t n ){
  if(!state || !src) return FSL_RC_MISUSE;
  else if(!n) return 0;
  else return (1 == fwrite(src, n, 1, state ? (FILE*)state : stdout))
         ? 0
         : FSL_RC_IO;
}

int fsl_input_f_FILE( void * state, void * dest, fsl_size_t * n ){
  FILE * f = (FILE*) state;
  if( !state || !dest || !n ) return FSL_RC_MISUSE;
  else if( !*n ) return FSL_RC_RANGE;
  *n = (fsl_size_t)fread( dest, 1, *n, f );
  return *n
    ? 0
    : (feof(f) ? 0 : FSL_RC_IO);
}

void fsl_finalizer_f_FILE( void * state, void * mem ){
  if(mem){
    fsl_fclose((FILE*)mem);
  }
}

int fsl_stream( fsl_input_f inF, void * inState,
                fsl_output_f outF, void * outState ){
  if(!inF || !outF) return FSL_RC_MISUSE;
  else{
    int rc = 0;
    enum { BufSize = 1024 * 4 };
    unsigned char buf[BufSize];
    fsl_size_t rn = BufSize;
    for( ; !rc &&
           (rn==BufSize)
           && (0==(rc=inF(inState, buf, &rn)));
         rn = BufSize){
      if(rn) rc = outF(outState, buf, rn);
      else break;
    }
    return rc;
  }
}

int fsl_stream_compare( fsl_input_f in1, void * in1State,
                        fsl_input_f in2, void * in2State ){
  enum { BufSize = 1024 * 2 };
  unsigned char buf1[BufSize];
  unsigned char buf2[BufSize];
  fsl_size_t rn1 = BufSize;
  fsl_size_t rn2 = BufSize;
  int rc;
  while(1){
    rc = in1(in1State, buf1, &rn1);
    if(rc) return -1;
    rc = in2(in2State, buf2, &rn2);
    if(rc) return 1;
    else if(rn1!=rn2){
      rc = (rn1<rn2) ? -1 : 1;
      break;
    }
    else if(0==rn1 && 0==rn2) return 0;
    rc = memcmp( buf1, buf2, rn1 );
    if(rc) break;
    rn1 = rn2 = BufSize;
  }
  return rc;
}

#undef MARKER
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






















































































































































































































































































































Deleted src/leaf.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 houses some of the "leaf"-related APIs.
*/
#include <assert.h>

#include "fossil-scm/fossil-internal.h"

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

int fsl_repo_leaves_rebuild(fsl_cx * f){
  fsl_db * db = f ? fsl_cx_db_repo(f) : NULL;
  return !db ? FSL_RC_MISUSE : fsl_db_exec_multi(db,
    "DELETE FROM leaf;"
    "INSERT OR IGNORE INTO leaf"
    "  SELECT cid FROM plink"
    "  EXCEPT"
    "  SELECT pid FROM plink"
    "   WHERE coalesce((SELECT value FROM tagxref"
                       " WHERE tagid=%d AND rid=plink.pid),'trunk')"
         " == coalesce((SELECT value FROM tagxref"
                       " WHERE tagid=%d AND rid=plink.cid),'trunk')",
    FSL_TAGID_BRANCH, FSL_TAGID_BRANCH
  );
}

fsl_int_t fsl_count_nonbranch_children(fsl_cx * f, fsl_id_t rid){
  int32_t rv = 0;
  int rc;
  fsl_db * db = f ? fsl_cx_db_repo(f) : NULL;
  if(!db || !db->dbh || (rid<=0)) return -1;
  rc = fsl_db_get_int32(db, &rv,
                        "SELECT count(*) FROM plink "
                        "WHERE pid=%"FSL_ID_T_PFMT" "
                        "AND isprim "
                        "AND coalesce((SELECT value FROM tagxref "
                        "WHERE tagid=%d AND rid=plink.pid), 'trunk')"
                        "=coalesce((SELECT value FROM tagxref "
                        "WHERE tagid=%d AND rid=plink.cid), 'trunk')",
                        rid, FSL_TAGID_BRANCH, FSL_TAGID_BRANCH);
  return rc ? -2 : rv;
}

bool fsl_rid_is_leaf(fsl_cx * f, fsl_id_t rid){
  int rv = -1;
  int rc;
  fsl_db * db = f ? fsl_cx_db_repo(f) : NULL;
  fsl_stmt * st = NULL;
  if(!db || !db->dbh || (rid<=0)) return 0;
  rc = fsl_db_prepare_cached(db, &st,
       "SELECT 1 FROM plink "
       "WHERE pid=?1 "
       "AND coalesce("
           "(SELECT value FROM tagxref "
            "WHERE tagid=%d AND rid=?1), "
          //"(SELECT value FROM config WHERE name='main-branch'), "
             "'trunk')"
           "=coalesce((SELECT value FROM tagxref "
             "WHERE tagid=%d "
             "AND rid=plink.cid), "
           //"(SELECT value FROM config WHERE name='main-branch'), "
             "'trunk')"
       "/*%s()*/",
       FSL_TAGID_BRANCH, FSL_TAGID_BRANCH, __func__);
  if(!rc){
    rc = fsl_stmt_bind_step(st, "R", rid);
    switch(rc){
      case FSL_RC_STEP_ROW:
        rv = 0;
        rc = 0;
        break;
      case 0:
        rv = 1;
        rc = 0;
        break;
      default:
        break;
    }
    fsl_stmt_cached_yield(st);
    assert(0==rv || 1==rv);
  }
  return rc ? 0 : (rv==1);
}

int fsl_repo_leaf_check(fsl_cx * f, fsl_id_t rid){
  fsl_db * const db = f ? fsl_cx_db_repo(f) : NULL;
  if(!db || !db->dbh) return FSL_RC_MISUSE;
  else if(rid<=0) return FSL_RC_RANGE;
  else {
    int rc = 0;
    bool isLeaf;
    fsl_cx_err_reset(f);
    isLeaf = fsl_rid_is_leaf(f, rid);
    rc = fsl_cx_err_get(f, NULL, NULL);
    if(!rc){
      fsl_stmt * st = NULL;
      if( isLeaf ){
        rc = fsl_db_prepare_cached(db, &st,
                                   "INSERT OR IGNORE INTO leaf VALUES"
                                   "(?) /*%s()*/",__func__);
      }else{
        rc = fsl_db_prepare_cached(db, &st,
                                   "DELETE FROM leaf WHERE rid=?"
                                   "/*%s()*/",__func__);
      }
      if(!rc && st){
        rc = fsl_stmt_bind_step(st, "R", rid);
        fsl_stmt_cached_yield(st);
        if(rc) rc = fsl_cx_uplift_db_error2(f, db, rc);
      }
    }
    return rc;
  }
}

int fsl_repo_leaf_eventually_check( fsl_cx * f, fsl_id_t rid){
  fsl_db * db = f ? fsl_cx_db_repo(f) : NULL;
  if(!f) return FSL_RC_MISUSE;
  else if(rid<=0) return FSL_RC_RANGE;
  else if(!db) return FSL_RC_NOT_A_REPO;
  else {
    fsl_stmt * parentsOf = NULL;
    int rc = fsl_db_prepare_cached(db, &parentsOf,
                            "SELECT pid FROM plink WHERE "
                            "cid=? AND pid>0"
                            "/*%s()*/",__func__);
    if(rc) return rc;
    rc = fsl_stmt_bind_id(parentsOf, 1, rid);
    if(!rc){
      rc = fsl_id_bag_insert(&f->cache.leafCheck, rid);
      while( !rc && (FSL_RC_STEP_ROW==fsl_stmt_step(parentsOf)) ){
        rc = fsl_id_bag_insert(&f->cache.leafCheck,
                               fsl_stmt_g_id(parentsOf, 0));
      }
    }
    fsl_stmt_cached_yield(parentsOf);
    return rc;
  }
}


int fsl_repo_leaf_do_pending_checks(fsl_cx *f){
  fsl_id_t rid;
  int rc = 0;
  for(rid=fsl_id_bag_first(&f->cache.leafCheck);
      !rc && rid; rid=fsl_id_bag_next(&f->cache.leafCheck,rid)){
    rc = fsl_repo_leaf_check(f, rid);
  }
  fsl_id_bag_clear(&f->cache.leafCheck);
  return rc;
}

int fsl_leaves_compute(fsl_cx * f, fsl_id_t vid,
                       fsl_leaves_compute_e closeMode){
  fsl_db * const db = fsl_needs_repo(f);
  if(!db) return FSL_RC_NOT_A_REPO;
  int rc = 0;

  /* Create the LEAVES table if it does not already exist.  Make sure
  ** it is empty.
  */
  rc = fsl_db_exec_multi(db,
    "CREATE TEMP TABLE IF NOT EXISTS leaves("
    "  rid INTEGER PRIMARY KEY"
    ");"
    "DELETE FROM leaves;"
  );
  if(rc) goto dberr;
  if( vid <= 0 ){
    rc = fsl_db_exec_multi(db,
      "INSERT INTO leaves SELECT leaf.rid FROM leaf"
    );
    if(rc) goto dberr;
  }
  if( vid>0 ){
    fsl_id_bag seen = fsl_id_bag_empty;     /* Descendants seen */
    fsl_id_bag pending = fsl_id_bag_empty;  /* Unpropagated descendants */
    fsl_stmt q1 = fsl_stmt_empty;      /* Query to find children of a check-in */
    fsl_stmt isBr = fsl_stmt_empty;    /* Query to check to see if a check-in starts a new branch */
    fsl_stmt ins = fsl_stmt_empty;     /* INSERT statement for a new record */

    /* Initialize the bags. */
    rc = fsl_id_bag_insert(&pending, vid);
    if(rc) goto cleanup;

    /* This query returns all non-branch-merge children of check-in :rid.
    **
    ** If a child is a merge of a fork within the same branch, it is
    ** returned.  Only merge children in different branches are excluded.
    */
    rc = fsl_db_prepare(db, &q1,
      "SELECT cid FROM plink"
      " WHERE pid=?1"
      "   AND (isprim"
      "        OR coalesce((SELECT value FROM tagxref"
                        "   WHERE tagid=%d AND rid=plink.pid), 'trunk')"
                        /* FIXME? main-branch? */
                 "=coalesce((SELECT value FROM tagxref"
                        "   WHERE tagid=%d AND rid=plink.cid), 'trunk'))"
                          /* FIXME? main-branch? */
                        ,
      FSL_TAGID_BRANCH, FSL_TAGID_BRANCH
    );
    if(rc) goto cleanup;
    /* This query returns a single row if check-in :rid is the first
    ** check-in of a new branch.
    */
    rc = fsl_db_prepare(db, &isBr,
       "SELECT 1 FROM tagxref"
       " WHERE rid=?1 AND tagid=%d AND tagtype=2"
       "   AND srcid>0",
       FSL_TAGID_BRANCH
    );
    if(rc) goto cleanup;

    /* This statement inserts check-in :rid into the LEAVES table.
    */
    rc = fsl_db_prepare(db, &ins,
                        "INSERT OR IGNORE INTO leaves VALUES(?1)");
    if(rc) goto cleanup;

    while( fsl_id_bag_count(&pending) ){
      fsl_id_t const rid = fsl_id_bag_first(&pending);
      unsigned cnt = 0;
      fsl_id_bag_remove(&pending, rid);
      fsl_stmt_bind_id(&q1, 1, rid);
      while( FSL_RC_STEP_ROW==(rc = fsl_stmt_step(&q1)) ){
        int const cid = fsl_stmt_g_id(&q1, 0);
        rc = fsl_id_bag_insert(&seen, cid);
        if(rc) break;
        rc = fsl_id_bag_insert(&pending, cid);
        if(rc) break;
        fsl_stmt_bind_id(&isBr, 1, cid);
        if( FSL_RC_STEP_DONE==fsl_stmt_step(&isBr) ){
          ++cnt;
        }
        fsl_stmt_reset(&isBr);
      }
      if(FSL_RC_STEP_DONE==rc) rc = 0;
      else if(rc) break;
      fsl_stmt_reset(&q1);
      if( cnt==0 && !fsl_rid_is_leaf(f, rid) ){
        ++cnt;
      }
      if( cnt==0 ){
        fsl_stmt_bind_id(&ins, 1, rid);
        rc = fsl_stmt_step(&ins);
        if(FSL_RC_STEP_DONE!=rc) break;
        rc = 0;
        fsl_stmt_reset(&ins);
      }
    }
    cleanup:
    fsl_stmt_finalize(&ins);
    fsl_stmt_finalize(&isBr);
    fsl_stmt_finalize(&q1);
    fsl_id_bag_clear(&pending);
    fsl_id_bag_clear(&seen);
    if(rc) goto dberr;
  }
  assert(!rc);
  switch(closeMode){
    case FSL_LEAVES_COMPUTE_OPEN:
      rc =
        fsl_db_exec_multi(db,
                          "DELETE FROM leaves WHERE rid IN"
                          "  (SELECT leaves.rid FROM leaves, tagxref"
                          "    WHERE tagxref.rid=leaves.rid "
                          "      AND tagxref.tagid=%d"
                          "      AND tagxref.tagtype>0)",
                          FSL_TAGID_CLOSED);
      if(rc) goto dberr;
      break;
    case FSL_LEAVES_COMPUTE_CLOSED:
      rc = 
        fsl_db_exec_multi(db,
                          "DELETE FROM leaves WHERE rid NOT IN"
                          "  (SELECT leaves.rid FROM leaves, tagxref"
                          "    WHERE tagxref.rid=leaves.rid "
                          "      AND tagxref.tagid=%d"
                          "      AND tagxref.tagtype>0)",
                          FSL_TAGID_CLOSED);
      if(rc) goto dberr;
      break;
    default: break;
  }

  end:
  return rc;
  dberr:
  assert(rc);
  rc = fsl_cx_uplift_db_error2(f, db, rc);
  goto end;
}

bool fsl_leaves_computed_has(fsl_cx * f){
  return fsl_db_exists(fsl_cx_db_repo(f),
                       "SELECT 1 FROM leaves");
}

fsl_int_t fsl_leaves_computed_count(fsl_cx * f){
  int32_t rv = -1;
  fsl_db * const db = fsl_cx_db_repo(f);
  int const rc = fsl_db_get_int32(db, &rv,
                                 "SELECT COUNT(*) FROM leaves");
  if(rc){
    fsl_cx_uplift_db_error2(f, db, rc);
    assert(-1==rv);
  }else{
    assert(rv>=0);
  }
  return rv;
}

fsl_id_t fsl_leaves_computed_latest(fsl_cx * f){
  fsl_id_t rv = 0;
  fsl_db * const db = fsl_cx_db_repo(f);
  int const rc =
    fsl_db_get_id(db, &rv,
                    "SELECT rid FROM leaves, event"
                    " WHERE event.objid=leaves.rid"
                    " ORDER BY event.mtime DESC");
  if(rc){
    fsl_cx_uplift_db_error2(f, db, rc);
    assert(!rv);
  }else{
    assert(rv>=0);
  }
  return rv;
}

void fsl_leaves_computed_cleanup(fsl_cx * f){
  fsl_db_exec(fsl_cx_db_repo(f), "DROP TABLE IF EXISTS leaves");
}

#undef MARKER
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
































































































































































































































































































































































































































































































































































































































































































































Added src/libfossil-config.h.





























































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#if !defined(_NET_FOSSIL_SCM_FSL_AMALGAMATION_CONFIG_H_INCLUDED_)
#define _NET_FOSSIL_SCM_FSL_AMALGAMATION_CONFIG_H_INCLUDED_ 1
#define FSL_AUX_SCHEMA "2015-01-24"
#define FSL_CONTENT_SCHEMA "2"
#define FSL_PACKAGE_NAME "libfossil"
#define FSL_LIBRARY_VERSION "0.0.1-alphabeta"
/* Tweak the following for your system... */
#if !defined(HAVE_COMPRESS)
#  define HAVE_COMPRESS 1
#endif
#if !defined(HAVE_DLFCN_H)
#  define HAVE_DLFCN_H 0
#endif
#if !defined(HAVE_DLOPEN)
#  define HAVE_DLOPEN 0
#endif
#if !defined(HAVE_GETADDRINFO)
#  define HAVE_GETADDRINFO 0
#endif
#if !defined(HAVE_INET_NTOP)
#  define HAVE_INET_NTOP 0
#endif
#if !defined(HAVE_INTTYPES_H)
#  define HAVE_INTTYPES_H 0
#endif
#if !defined(HAVE_LIBDL)
#  define HAVE_LIBDL 0
#endif
#if !defined(HAVE_LIBLTDL)
#  define HAVE_LIBLTDL 0
#endif
#if !defined(_WIN32)
#if !defined(HAVE_LSTAT)
#  define HAVE_LSTAT 1
#endif
#if !defined(HAVE_LTDL_H)
#  define HAVE_LTDL_H 0
#endif
#if !defined(HAVE_LT_DLOPEN)
#  define HAVE_LT_DLOPEN 0
#endif
#if !defined(HAVE_OPENDIR)
#  define HAVE_OPENDIR 1
#endif
#if !defined(HAVE_PIPE)
#  define HAVE_PIPE 1
#endif
#if !defined(HAVE_STAT)
#  define HAVE_STAT 1
#endif
#if !defined(HAVE_STDINT_H)
#  define HAVE_STDINT_H 1
#endif
#if !defined(_DEFAULT_SOURCE)
#  define _DEFAULT_SOURCE 1
#endif
#if !defined(_XOPEN_SOURCE)
#  define _XOPEN_SOURCE 500
#endif
#else
#if !defined(HAVE_LSTAT)
#  define HAVE_LSTAT 0
#endif
#if !defined(HAVE_LTDL_H)
#  define HAVE_LTDL_H 0
#endif
#if !defined(HAVE_LT_DLOPEN)
#  define HAVE_LT_DLOPEN 0
#endif
#if !defined(HAVE_OPENDIR)
#  define HAVE_OPENDIR 1
#endif
#if !defined(HAVE_PIPE)
#  define HAVE_PIPE 0
#endif
#if !defined(HAVE_STAT)
#  define HAVE_STAT 0
#endif
#if !defined(HAVE_STDINT_H)
#  define HAVE_STDINT_H 1
#endif
#endif
/* _WIN32 */


#define FSL_LIB_VERSION_HASH "5d405b727bd79bf2abb5349407432178394a962a"
#define FSL_LIB_VERSION_TIMESTAMP "2021-08-23 15:36:42.964 UTC"
#define FSL_LIB_CONFIG_TIME "2021-08-23 15:38 GMT"
#if defined(_MSC_VER)
#define FSL_PLATFORM_OS "windows"
#define FSL_PLATFORM_IS_WINDOWS 1
#define FSL_PLATFORM_IS_UNIX 0
#define FSL_PLATFORM_PLATFORM "windows"
#define FSL_PLATFORM_PATH_SEPARATOR ";"
#define FSL_CHECKOUTDB_NAME "./_FOSSIL_"
/* define a __func__ compatibility macro */
#if _MSC_VER < 1500    /* (vc9.0; dev studio 2008) */
/* sorry; cant do much better than nothing at all on those earlier ones */
#define __func__ "(func)"
#else
#define __func__ __FUNCTION__
#endif
/* for the time being at least, don't complain about there being secure crt alternatives: */
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif
/* for the time being at least, don't complain about using POSIX names instead of ISO C++: */
#pragma warning ( disable : 4996 )
/* for the time being at least, suppresss some int conversion warnings */
#pragma warning ( disable : 4244 )     /*'fsl_size_t' to 'int'; this masks other problems that should be fixed*/
#pragma warning ( disable : 4761 )     /*'integral size mismatch in argument'; more size_t problems*/
#pragma warning ( disable : 4267 )     /*'size_t' to 'int'; crops up especially in 64-bit builds*/
/* these were extracted from fossil's unistd.h */
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#include <io.h>
#elif defined(__MINGW32__)
#define FSL_PLATFORM_OS "mingw"
#define FSL_PLATFORM_IS_WINDOWS 1
#define FSL_PLATFORM_IS_UNIX 0
#define FSL_PLATFORM_PLATFORM "windows"
#define FSL_PLATFORM_PATH_SEPARATOR ";"
#define FSL_CHECKOUTDB_NAME "./.fslckout"
#elif defined(__CYGWIN__)
#define FSL_PLATFORM_OS "cygwin"
#define FSL_PLATFORM_IS_WINDOWS 0
#define FSL_PLATFORM_IS_UNIX 1
#define FSL_PLATFORM_PLATFORM "unix"
#define FSL_PLATFORM_PATH_SEPARATOR ":"
#define FSL_CHECKOUTDB_NAME "./_FOSSIL_"
#else
#define FSL_PLATFORM_OS "unknown"
#define FSL_PLATFORM_IS_WINDOWS 0
#define FSL_PLATFORM_IS_UNIX 1
#define FSL_PLATFORM_PLATFORM "unix"
#define FSL_PLATFORM_PATH_SEPARATOR ":"
#define FSL_CHECKOUTDB_NAME "./.fslckout"
#endif


#endif
/* _NET_FOSSIL_SCM_FSL_AMALGAMATION_CONFIG_H_INCLUDED_ */

Added src/libfossil.c.

more than 10,000 changes

Added src/libfossil.h.

more than 10,000 changes

Deleted src/list.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 houses the implementations for the fsl_list routines.
*/

#include "fossil-scm/fossil-internal.h"
#include <assert.h>
#include <stdlib.h> /* malloc() and friends, qsort() */
#include <memory.h> /* memset() */
int fsl_list_reserve( fsl_list * self, fsl_size_t n )
{
  if( !self ) return FSL_RC_MISUSE;
  else if(0 == n){
    if(0 == self->capacity) return 0;
    fsl_free(self->list);
    *self = fsl_list_empty;
    return 0;
  }
  else if( self->capacity >= n ){
    return 0;
  }
  else{
    size_t const sz = sizeof(void*) * n;
    void* * m = (void**)fsl_realloc( self->list, sz );
    if( !m ) return FSL_RC_OOM;
    memset( m + self->capacity, 0, (sizeof(void*)*(n-self->capacity)));
    self->capacity = n;
    self->list = m;
    return 0;
  }
}

void fsl_list_swap( fsl_list * lhs, fsl_list * rhs ){
  fsl_list tmp = *lhs;
  *rhs = *lhs;
  *lhs = tmp;
}

int fsl_list_append( fsl_list * self, void* cp ){
  if( !self ) return FSL_RC_MISUSE;
  assert(self->used <= self->capacity);
  if(self->used == self->capacity){
    int rc;
    fsl_size_t const cap = self->capacity
      ? (self->capacity * 2)
      : 10;
    rc = fsl_list_reserve(self, cap);
    if(rc) return rc;
  }
  self->list[self->used++] = cp;
  if(self->used<self->capacity) self->list[self->used]=NULL;
  return 0;
}

int fsl_list_v_fsl_free(void * obj, void * visitorState ){
  if(obj) fsl_free( obj );
  return 0;
}

int fsl_list_clear( fsl_list * self, fsl_list_visitor_f childFinalizer,
                    void * finalizerState ){
  /*
    TODO: manually traverse the list and set each list entry for which
    the finalizer succeeds to NULL, so that we can provide
    well-defined behaviour if childFinalizer() fails and we abort the
    loop.
   */
  int rc = fsl_list_visit(self, 0, childFinalizer, finalizerState );
  if(!rc) fsl_list_reserve(self, 0);
  return rc;
}

void fsl_list_visit_free( fsl_list * self, bool freeListMem ){
  fsl_list_visit(self, 0, fsl_list_v_fsl_free, NULL );
  if(freeListMem) fsl_list_reserve(self, 0);
  else self->used = 0;
}


int fsl_list_visit( fsl_list const * self, int order,
                    fsl_list_visitor_f visitor,
                    void * visitorState ){
  int rc = FSL_RC_OK;
  if( self && self->used && visitor ){
    fsl_int_t i = 0;
    fsl_int_t pos = (order<0) ? self->used-1 : 0;
    fsl_int_t step = (order<0) ? -1 : 1;
    for( rc = 0; (i < (fsl_int_t)self->used)
           && (0 == rc); ++i, pos+=step ){
      void* obj = self->list[pos];
      if(obj) rc = visitor( obj, visitorState );
      if( obj != self->list[pos] ){
        --i;
        if(order>=0) pos -= step;
      }
    }
  }
  return rc;
}


int fsl_list_visit_p( fsl_list * self, int order,
                      bool shiftIfNulled,
                      fsl_list_visitor_f visitor, void * visitorState )
{
  int rc = FSL_RC_OK;
  if( self && self->used && visitor ){
    fsl_int_t i = 0;
    fsl_int_t pos = (order<0) ? self->used-1 : 0;
    fsl_int_t step = (order<0) ? -1 : 1;
    for( rc = 0; (i < (fsl_int_t)self->used)
           && (0 == rc); ++i, pos+=step ){
      void* obj = self->list[pos];
      if(obj) {
        assert((order<0) && "TEST THAT THIS WORKS WITH IN-ORDER!");
        rc = visitor( &self->list[pos], visitorState );
        if( shiftIfNulled && !self->list[pos]){
          fsl_int_t x = pos;
          fsl_int_t const to = self->used-pos;

          assert( to < (fsl_int_t) self->capacity );
          for( ; x < to; ++x ) self->list[x] = self->list[x+1];
          if( x < (fsl_int_t)self->capacity ) self->list[x] = 0;
          --i;
          --self->used;
          if(order>=0) pos -= step;
        }
      }
    }
  }
  return rc;
}

void fsl_list_sort( fsl_list * li,
                    fsl_generic_cmp_f cmp){
  if(li && li->used>1){
    qsort( li->list, li->used, sizeof(void*), cmp );
  }
}



fsl_int_t fsl_list_index_of( fsl_list const * li,
                             void const * key,
                             fsl_generic_cmp_f cmpf ){
  fsl_size_t i;
  void const * p;
  for(i = 0; i < li->used; ++i){
    p = li->list[i];
    if(!p){
      if(!key) return (fsl_int_t)i;
      else continue;
    }else if((p==key) ||
            (0==cmpf( key, p )) ){
      return (fsl_int_t)i;
    }
  }
  return -1;
}


fsl_int_t fsl_list_index_of_cstr( fsl_list const * li,
                                  char const * key ){
  return fsl_list_index_of(li, key, fsl_strcmp_cmp);
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
































































































































































































































































































































































Deleted src/md5.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/*
   The code is modified for use in fossil (then libfossil).  The
   original header comment follows:
*/
/*
 * This code implements the MD5 message-digest algorithm.
 * The algorithm is due to Ron Rivest.  This code was
 * written by Colin Plumb in 1993, no copyright is claimed.
 * This code is in the public domain; do with it what you wish.
 *
 * Equivalent code is available from RSA Data Security, Inc.
 * This code has been tested against that, and is equivalent,
 * except that you don't need to include two pages of legalese
 * with every copy.
 *
 * To compute the message digest of a chunk of bytes, declare an
 * MD5Context structure, pass it to MD5Init, call MD5Update as
 * needed on buffers full of bytes, and then call MD5Final, which
 * will fill a supplied 16-byte array with the digest.
 */
#include <string.h>
#include <stdio.h>
#include "fossil-scm/fossil.h"
#include <errno.h>

#if defined(__i386__) || defined(__x86_64__) || defined(_WIN32)
# define byteReverse(A,B)
#else
/*
 * Convert an array of integers to little-endian.
 * Note: this code is a no-op on little-endian machines.
 */
static void byteReverse (unsigned char *buf, unsigned longs){
    uint32_t t;
    do {
        t = (uint32_t)((unsigned)buf[3]<<8 | buf[2]) << 16 |
            ((unsigned)buf[1]<<8 | buf[0]);
        *(uint32_t *)buf = t;
        buf += 4;
    } while (--longs);
}
#endif

/* The four core functions - F1 is optimized somewhat */

/* #define F1(x, y, z) (x & y | ~x & z) */
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))

/* This is the central step in the MD5 algorithm. */
#define MD5STEP(f, w, x, y, z, data, s)                         \
    ( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )

/*
 * The core of the MD5 algorithm, this alters an existing MD5 hash to
 * reflect the addition of 16 longwords of new data.  MD5Update blocks
 * the data and converts bytes into longwords for this routine.
 */
static void MD5Transform(uint32_t buf[4], const uint32_t in[16]){
    register uint32_t a, b, c, d;

    a = buf[0];
    b = buf[1];
    c = buf[2];
    d = buf[3];

    MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478,  7);
    MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12);
    MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17);
    MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22);
    MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf,  7);
    MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12);
    MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17);
    MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22);
    MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8,  7);
    MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12);
    MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17);
    MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22);
    MD5STEP(F1, a, b, c, d, in[12]+0x6b901122,  7);
    MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12);
    MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17);
    MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22);

    MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562,  5);
    MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340,  9);
    MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14);
    MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20);
    MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d,  5);
    MD5STEP(F2, d, a, b, c, in[10]+0x02441453,  9);
    MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14);
    MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20);
    MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6,  5);
    MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6,  9);
    MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14);
    MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20);
    MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905,  5);
    MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8,  9);
    MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14);
    MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20);

    MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942,  4);
    MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11);
    MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16);
    MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23);
    MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44,  4);
    MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11);
    MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16);
    MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23);
    MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6,  4);
    MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11);
    MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16);
    MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23);
    MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039,  4);
    MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11);
    MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16);
    MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23);

    MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244,  6);
    MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10);
    MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15);
    MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21);
    MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3,  6);
    MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10);
    MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15);
    MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21);
    MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f,  6);
    MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10);
    MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15);
    MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21);
    MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82,  6);
    MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10);
    MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15);
    MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21);

    buf[0] += a;
    buf[1] += b;
    buf[2] += c;
    buf[3] += d;
}

const fsl_md5_cx fsl_md5_cx_empty = fsl_md5_cx_empty_m;
/*
 * Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
 * initialization constants.
 */
void fsl_md5_init(fsl_md5_cx *ctx){
    *ctx = fsl_md5_cx_empty;
}

/*
 * Update context to reflect the concatenation of another buffer full
 * of bytes.
 */
void fsl_md5_update(fsl_md5_cx *ctx, void const * buf_, fsl_size_t len){
    const unsigned char * buf = (const unsigned char *)buf_;
    uint32_t t;
    
    /* Update bitcount */
    
    t = ctx->bits[0];
    if ((ctx->bits[0] = t + ((uint32_t)len << 3)) < t)
        ctx->bits[1]++; /* Carry from low to high */
    ctx->bits[1] += len >> 29;
    
    t = (t >> 3) & 0x3f;    /* Bytes already in shsInfo->data */

    /* Handle any leading odd-sized chunks */

    if ( t ) {
        unsigned char *p = (unsigned char *)ctx->in + t;

        t = 64-t;
        if (len < t) {
            memcpy(p, buf, len);
            return;
        }
        memcpy(p, buf, t);
        byteReverse(ctx->in, 16);
        MD5Transform(ctx->buf, (uint32_t *)ctx->in);
        buf += t;
        len -= t;
    }

    /* Process data in 64-byte chunks */

    while (len >= 64) {
        memcpy(ctx->in, buf, 64);
        byteReverse(ctx->in, 16);
        MD5Transform(ctx->buf, (uint32_t *)ctx->in);
        buf += 64;
        len -= 64;
    }

    /* Handle any remaining bytes of data. */

    memcpy(ctx->in, buf, len);
}

/*
 * Final wrapup - pad to 64-byte boundary with the bit pattern 
 * 1 0* (64-bit count of bits processed, MSB-first)
 */
void fsl_md5_final(fsl_md5_cx * ctx, unsigned char digest[16]){
    unsigned count;
    unsigned char *p;

    /* Compute number of bytes mod 64 */
    count = (ctx->bits[0] >> 3) & 0x3F;

    /* Set the first char of padding to 0x80.  This is safe since there is
       always at least one byte free */
    p = ctx->in + count;
    *p++ = 0x80;

    /* Bytes of padding needed to make 64 bytes */
    count = 64 - 1 - count;

    /* Pad out to 56 mod 64 */
    if (count < 8) {
        /* Two lots of padding:  Pad the first block to 64 bytes */
        memset(p, 0, count);
        byteReverse(ctx->in, 16);
        MD5Transform(ctx->buf, (uint32_t *)ctx->in);

        /* Now fill the next block with 56 bytes */
        memset(ctx->in, 0, 56);
    } else {
        /* Pad block to 56 bytes */
        memset(p, 0, count-8);
    }
    byteReverse(ctx->in, 14);

    /* Append length in bits and transform */
    memcpy(&ctx->in[14*sizeof(uint32_t)], ctx->bits, 2*sizeof(uint32_t));

    MD5Transform(ctx->buf, (uint32_t *)ctx->in);
    byteReverse((unsigned char *)ctx->buf, 4);
    memcpy(digest, ctx->buf, 16);
    memset(ctx, 0, sizeof(*ctx));    /* In case it's sensitive */
}

void fsl_md5_digest_to_base16(unsigned char *digest, char *zBuf){
    static char const zEncode[] = "0123456789abcdef";
    int i, j;

    for(j=i=0; i<16; i++){
        int a = digest[i];
        zBuf[j++] = zEncode[(a>>4)&0xf];
        zBuf[j++] = zEncode[a & 0xf];
    }
    zBuf[j] = 0;
}

#if 0
/* The symlink-hashing code here may be needed at some point... */
int fsl_md5sum_file(const char *zFilename, fsl_buffer *pCksum){
  if(!zFilename || !pCksum) return FSL_RC_MISUSE;
  else{
    /* Requires v1 code which has not yet been ported in. */
    FILE *in;
    fsl_md5_cx ctx;
    unsigned char zResult[20];
    char zBuf[10240];

    if( fsl_wd_islink(zFilename) ){
      /* Instead of file content, return md5 of link destination path */
      Blob destinationPath;
      int rc;
      
      blob_read_link(&destinationPath, zFilename);
      rc = sha1sum_blob(&destinationPath, pCksum);
      blob_reset(&destinationPath);
      return rc;
    }

    in = fossil_fopen(zFilename,"rb");
    if( in==0 ){
      return 1;
    }
    fsl_sha1_init(&ctx);
    for(;;){
      int n;
      n = fread(zBuf, 1, sizeof(zBuf), in);
      if( n<=0 ) break;
      fsl_sha1_update(&ctx, (unsigned char*)zBuf, (unsigned)n);
    }
    fsl_fclose(in);
    blob_zero(pCksum);
    blob_resize(pCksum, 40);
    fsl_sha1_final(&ctx, zResult);
    fsl_sha1_digest_to_base16(zResult, blob_buffer(pCksum));
    return 0;
  }
}
#endif

int fsl_md5sum_buffer(fsl_buffer const *pIn, fsl_buffer *pCksum){
  if(!pIn || !pCksum) return FSL_RC_MISUSE;
  else{
    fsl_md5_cx ctx = fsl_md5_cx_empty;
    unsigned char zResult[20];
    int rc;
    fsl_md5_update(&ctx, pIn->mem, pIn->used);
    fsl_buffer_reuse(pCksum);
    rc = fsl_buffer_resize(pCksum, FSL_STRLEN_MD5/*resize() adds 1 for NUL*/);
    if(!rc){
      fsl_md5_final(&ctx, zResult);
      fsl_md5_digest_to_base16(zResult, fsl_buffer_str(pCksum));
    }
    return rc;
  }
}

char *fsl_md5sum_cstr(const char *zIn, fsl_int_t len){
  if(!zIn || !len) return NULL;
  else{
    fsl_md5_cx ctx;
    unsigned char zResult[20];
    char * zDigest = (char *)fsl_malloc(FSL_STRLEN_MD5+1);
    if(!zDigest) return NULL;
    fsl_md5_init(&ctx);
    fsl_md5_update(&ctx, zIn,
                    (len<0) ? fsl_strlen(zIn) : (fsl_size_t)len);
    fsl_md5_final(&ctx, zResult);
    fsl_md5_digest_to_base16(zResult, zDigest);
    return zDigest;
  }
}

int fsl_md5sum_stream(fsl_input_f src, void * srcState, fsl_buffer *pCksum){
    fsl_md5_cx ctx;
    int rc;
    unsigned char zResult[20];
    enum { BufSize = 1024 * 4 };
    unsigned char zBuf[BufSize];
    if(!src || !pCksum) return FSL_RC_MISUSE;
    fsl_md5_init(&ctx);
    for(;;){
      fsl_size_t read = (fsl_size_t)BufSize;
      rc = src(srcState, zBuf, &read);
      if(rc) return rc;
      else if(read) fsl_md5_update(&ctx, (unsigned char*)zBuf, read);
      if(read < (fsl_size_t)BufSize) break;
    }
    fsl_buffer_reuse(pCksum);
    rc = fsl_buffer_resize(pCksum, FSL_STRLEN_MD5);
    if(!rc){
      fsl_md5_final(&ctx, zResult);
      fsl_md5_digest_to_base16(zResult, fsl_buffer_str(pCksum));
    }
    return rc;
}



#if 0
void fsl_md5_to_base16(fsl_md5_cx const * cx, char *zBuf){
    static char const zEncode[] = "0123456789abcdef";
    int i, j;

    for(j=i=0; i<16; i++){
        int a = digest[i];
        zBuf[j++] = zEncode[(a>>4)&0xf];
        zBuf[j++] = zEncode[a & 0xf];
    }
    zBuf[j] = 0;
}
#endif


#if 0
/*
   Add the content of a blob to the incremental MD5 checksum.
*/
void md5sum_step_blob(Blob *p){
    md5sum_step_text(blob_buffer(p), blob_size(p));
}
#endif

int fsl_md5sum_filename(const char *zFilename, fsl_buffer *pCksum){
  if(!zFilename || !pCksum) return FSL_RC_MISUSE;
  else{
    int rc;
    FILE *in = fsl_fopen(zFilename, "rb");
    if(!in) rc = FSL_RC_IO;
    else{
      rc = fsl_md5sum_stream(fsl_input_f_FILE, in, pCksum);
      fsl_fclose(in);
    }
    return rc;
  }
}

void fsl_md5_update_buffer(fsl_md5_cx *cx, fsl_buffer const * b){
  if(b->used) fsl_md5_update(cx, b->mem, b->used);
}

void fsl_md5_update_cstr(fsl_md5_cx *cx, char const * str, fsl_int_t len){
  if(len<0) len = fsl_strlen(str);
  if(len>0) fsl_md5_update(cx, str, (fsl_size_t)len);
}

int fsl_md5_update_stream(fsl_md5_cx *ctx,
                          fsl_input_f src, void * srcState){
    int rc;
    enum { BufSize = 1024 * 4 };
    unsigned char zBuf[BufSize];
    if(!ctx || !src) return FSL_RC_MISUSE;
    for(;;){
      fsl_size_t read = (fsl_size_t)BufSize;
      rc = src(srcState, zBuf, &read);
      if(rc) return rc;
      else if(read) fsl_md5_update(ctx, (unsigned char*)zBuf, read);
      if(read < (fsl_size_t)BufSize) break;
    }
    return 0;
}

int fsl_md5_update_filename(fsl_md5_cx *cx, char const * fname){
  if(!cx || !fname) return FSL_RC_MISUSE;
  else{
    int rc;
    FILE *in = fsl_fopen(fname, "rb");
    if(in) rc = fsl_errno_to_rc(errno,FSL_RC_IO);
    else {
      rc = fsl_md5_update_stream(cx, fsl_input_f_FILE, in);
      fsl_fclose(in);
    }
    return rc;
  }
}


#undef F1
#undef F2
#undef F3
#undef F4
#undef MD5STEP
#undef byteReverse
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




















































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/merge3.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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).
*/
#include "fossil-scm/fossil.h"
#include <assert.h>
#include <memory.h>
#include <stdlib.h>
#include <string.h> /* memmove()/strlen() */

#if 0
#define FDEBUG(X)  X
#define ISFDEBUG 1
#define MARKER(pfexp)                                               \
  do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__);   \
    printf pfexp;                                                   \
  } while(0)
#else
#define FDEBUG(X)
#define ISFDEBUG 0
#define MARKER(pfexp) (void)0
#endif

// Only for testing/debugging..

/* The minimum of two integers */
#define mymin(A,B)  ((A)<(B)?(A):(B))

/**
   Compare N lines of text from pV1 and pV2. If the lines are the
   same, return true. Return false if one or more of the N lines are
   different.

   The cursors on both pV1 and pV2 is unchanged by this comparison.
*/
static
bool sameLines(fsl_buffer const *pV1, fsl_buffer const *pV2,
               int N){
  unsigned char const *z1;
  unsigned char const *z2;
  fsl_size_t i;
  unsigned char c;
  if( !N ) return true;
  z1 = pV1->mem + pV1->cursor;
  z2 = pV2->mem + pV2->cursor;
  for(i=0; (c=z1[i])==z2[i]; ++i){
    if( c=='\n' || !c ){
      --N;
      if( !N || !c ) return true;
    }
  }
  return false;
}

/**
   Look at the next edit triple in both aC1 and aC2.  (An "edit triple" is
   three integers describing the number of copies, deletes, and inserts in
   moving from the original to the edited copy of the file.) If the three
   integers of the edit triples describe an identical edit, then return 1.
   If the edits are different, return 0.

   aC1 = the array of edit integers for pV1.

   aC2 = the array of edit integers for pV2.
*/
static
bool sameEdit(int const *aC1, int const *aC2,
              fsl_buffer const *pV1, fsl_buffer const *pV2){
#if 0
  if( aC1[0]!=aC2[0] ) return 0;
  if( aC1[1]!=aC2[1] ) return 0;
  if( aC1[2]!=aC2[2] ) return 0;
  if( sameLines(pV1, pV2, aC1[2]) ) return 1;
  return 0;
#else
  if( aC1[0]!=aC2[0]
      || aC1[1]!=aC2[1]
      || aC1[2]!=aC2[2] ) return false;
  return sameLines(pV1, pV2, aC1[2]);
#endif
}


/**
   The aC[] array contains triples of integers. Within each triple,
   the elements are:

   (0)  The number of lines to copy
   (1)  The number of lines to delete
   (2)  The number of liens to insert

   Suppose we want to advance over sz lines of the original file.
   This routine returns true if that advance would land us on a copy
   operation. It returns false if the advance would end on a delete.
*/
static
bool ends_at_CPY(int *aC, int sz){
  while( sz>0 && (aC[0]>0 || aC[1]>0 || aC[2]>0) ){
    if( aC[0]>=sz ) return true;
    sz -= aC[0];
    if( aC[1]>sz ) return false;
    sz -= aC[1];
    aC += 3;
  }
  return true;
}

/**
   pSrc contains an edited file where aC[] describes the edit.  Part
   of pSrc has already been output.  This routine outputs additional
   lines of pSrc - lines that correspond to the next sz lines of the
   original unedited file.

   Note that sz counts the number of lines of text in the original
   file, but text is output from the edited file, so the number of
   lines transfer to pOut might be different from sz. Fewer lines
   appear in pOut if there are deletes. More lines appear if there
   are inserts.

   The aC[] array is updated and the new index into aC[] is returned
   via the final argument.

   Returns 0 on success, FSL_RC_OOM on allocation error.
*/
static
int output_one_side(fsl_buffer *pOut,
                    fsl_buffer *pSrc,
                    int *aC,
                    int i,
                    int sz,
                    int *newIndex){
  int rc = 0;
  while( sz>0 ){
    if( aC[i]==0 && aC[i+1]==0 && aC[i+2]==0 ) break;
    if( aC[i]>=sz ){
      rc = fsl_buffer_copy_lines(pOut, pSrc, sz);
      if(rc) break;
      aC[i] -= sz;
      break;
    }
    rc = fsl_buffer_copy_lines(pOut, pSrc, aC[i]);
    if(!rc) rc = fsl_buffer_copy_lines(pOut, pSrc, aC[i+2]);
    if(rc) break;
    sz -= aC[i] + aC[i+1];
    i += 3;
  }
  if(!rc) *newIndex = i;
  return rc;
}

/**
   Return true if the input blob contains any CR/LF pairs on the first
   ten lines. This should be enough to detect files that use mainly
   CR/LF line endings without causing a performance impact for LF only
   files.
*/
static
bool contains_crlf(fsl_buffer const *p){
  fsl_size_t i;
  fsl_size_t j = 0;
  const uint16_t maxL = 10; //Max lines to check
  unsigned const char *z = p->mem;
  fsl_size_t const n = p->used+1;
  for(i=1; i<n; ){
    if( z[i-1]=='\r' && z[i]=='\n' ) return true;
    while( i<n && z[i]!='\n' ){ ++i; }
    ++j;
    if( j>maxL ) break;
  }
  return false;
}

/**
   Ensure that the text in p, if not empty, ends with a new line. If
   useCrLf is true adds "\r\n" otherwise "\n".  Returns 0 on success
   or p is empty, and FSL_RC_OOM on OOM.
*/
static
int ensure_line_end(fsl_buffer *p, bool useCrLf){
  int rc = 0;
  if( !p->used ) return 0;
  if( p->mem[p->used-1]!='\n' ){
    rc = fsl_buffer_append(p, useCrLf ? "\r\n" : "\n", useCrLf ? 2 : 1);
  }
  return rc;
}

/**
   Returns an array of bytes representing the byte-order-mark for
   UTF-8. If pnByte is not NULL, the number of bytes in the BOM (3)
   is written there.
*/
//static
const unsigned char *get_utf8_bom(unsigned int *pnByte){
  static const unsigned char bom[] = {
    0xef, 0xbb, 0xbf, 0x00, 0x00, 0x00
  };
  if( pnByte ) *pnByte = 3;
  return bom;
}

/**
   Returns true if given blob starts with a UTF-8
   byte-order-mark (BOM).
*/
static
bool starts_with_utf8_bom(fsl_buffer const *p, unsigned int *n){
  unsigned const char *z = p->mem;
  unsigned int bomSize = 0;
  const unsigned char *bom = get_utf8_bom(&bomSize);
  if( n ) *n = bomSize;
  return (p->used<bomSize)
    ? false
    : memcmp(z, bom, bomSize)==0;
}

/**
   Text of boundary markers for merge conflicts.

   NEVER, EVER change these. They MUST match the ones used
   by fossil.
*/
static
const char *const mergeMarker[] = {
 /*123456789 123456789 123456789 123456789 123456789 123456789 123456789*/
  "<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<<<<<",
  "||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||||||",
  "======= MERGED IN content follows ==================================",
  ">>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
};

static fsl_int_t assert_mema_lengths(){
  static const fsl_int_t mmLen = 68;
  static bool once = true;
  if(once){
    once = false;
    assert(sizeof(mergeMarker)/sizeof(mergeMarker[0]) == 4);
    assert((fsl_int_t)fsl_strlen(mergeMarker[0])==mmLen);
    assert((fsl_int_t)fsl_strlen(mergeMarker[1])==mmLen);
    assert((fsl_int_t)fsl_strlen(mergeMarker[2])==mmLen);
    assert((fsl_int_t)fsl_strlen(mergeMarker[3])==mmLen);
  }
  return mmLen;
}

int fsl_buffer_merge3(fsl_buffer *pPivot, fsl_buffer *pV1, fsl_buffer *pV2, fsl_buffer *pOut,
                      unsigned int *conflictCount){
  int *aC1 = 0;          /* Changes from pPivot to pV1 */
  int *aC2 = 0;          /* Changes from pPivot to pV2 */
  int i1, i2;            /* Index into aC1[] and aC2[] */
  int nCpy, nDel, nIns;  /* Number of lines to copy, delete, or insert */
  int limit1, limit2;    /* Sizes of aC1[] and aC2[] */
  int rc = 0;
  unsigned int nConflict = 0;     /* Number of merge conflicts seen so far */
  bool useCrLf = false;
  const fsl_int_t mmLen = assert_mema_lengths();

#define RC if(rc) { \
    MARKER(("rc=%s\n", fsl_rc_cstr(rc))); goto end; } (void)0
  fsl_buffer_reuse(pOut);         /* Merge results stored in pOut */
  
  /* If both pV1 and pV2 start with a UTF-8 byte-order-mark (BOM),
  ** keep it in the output. This should be secure enough not to cause
  ** unintended changes to the merged file and consistent with what
  ** users are using in their source files.
  */
  if( starts_with_utf8_bom(pV1, 0) && starts_with_utf8_bom(pV2, 0) ){
    rc = fsl_buffer_append(pOut, get_utf8_bom(0), 3);
    RC;
  }

  /* Check once to see if both pV1 and pV2 contains CR/LF endings.
  ** If true, CR/LF pair will be used later to append the
  ** boundary markers for merge conflicts.
  */
  if( contains_crlf(pV1) && contains_crlf(pV2) ){
    useCrLf = true;
  }

  /* Compute the edits that occur from pPivot => pV1 (into aC1)
  ** and pPivot => pV2 (into aC2).  Each of the aC1 and aC2 arrays is
  ** an array of integer triples.  Within each triple, the first integer
  ** is the number of lines of text to copy directly from the pivot,
  ** the second integer is the number of lines of text to omit from the
  ** pivot, and the third integer is the number of lines of text that are
  ** inserted.  The edit array ends with a triple of 0,0,0.
  */
  rc = fsl_diff_text_raw(pPivot, pV1, 0, &aC1);
  if(!rc) rc = fsl_diff_text_raw(pPivot, pV2, 0, &aC2);
  RC;
  assert(aC1 && aC2);

  /* Rewind inputs:  Needed to reconstruct output */
  fsl_buffer_rewind(pV1);
  fsl_buffer_rewind(pV2);
  fsl_buffer_rewind(pPivot);

  /* Determine the length of the aC1[] and aC2[] change vectors */
  for(i1=0; aC1[i1] || aC1[i1+1] || aC1[i1+2]; i1+=3){}
  limit1 = i1;
  for(i2=0; aC2[i2] || aC2[i2+1] || aC2[i2+2]; i2+=3){}
  limit2 = i2;

  FDEBUG(
    for(i1=0; i1<limit1; i1+=3){
      printf("c1: %4d %4d %4d\n", aC1[i1], aC1[i1+1], aC1[i1+2]);
    }
    for(i2=0; i2<limit2; i2+=3){
      printf("c2: %4d %4d %4d\n", aC2[i2], aC2[i2+1], aC2[i2+2]);
    }
  )

  /* Loop over the two edit vectors and use them to compute merged text
  ** which is written into pOut.  i1 and i2 are multiples of 3 which are
  ** indices into aC1[] and aC2[] to the edit triple currently being
  ** processed
  */
  i1 = i2 = 0;
  while( i1<limit1 && i2<limit2 ){
    FDEBUG( printf("%d: %2d %2d %2d   %d: %2d %2d %2d\n",
           i1/3, aC1[i1], aC1[i1+1], aC1[i1+2],
           i2/3, aC2[i2], aC2[i2+1], aC2[i2+2]); )

    if( aC1[i1]>0 && aC2[i2]>0 ){
      /* Output text that is unchanged in both V1 and V2 */
      nCpy = mymin(aC1[i1], aC2[i2]);
      FDEBUG( printf("COPY %d\n", nCpy); )
      rc = fsl_buffer_copy_lines(pOut, pPivot, (fsl_size_t)nCpy);
      if(!rc) rc = fsl_buffer_copy_lines(0, pV1, (fsl_size_t)nCpy);
      if(!rc) rc = fsl_buffer_copy_lines(0, pV2, (fsl_size_t)nCpy);
      RC;
      aC1[i1] -= nCpy;
      aC2[i2] -= nCpy;
    }else
    if( aC1[i1] >= aC2[i2+1] && aC1[i1]>0 && aC2[i2+1]+aC2[i2+2]>0 ){
      /* Output edits to V2 that occurs within unchanged regions of V1 */
      nDel = aC2[i2+1];
      nIns = aC2[i2+2];
      FDEBUG( printf("EDIT -%d+%d left\n", nDel, nIns); )
      rc = fsl_buffer_copy_lines(0, pPivot, (fsl_size_t)nDel);
      if(!rc) rc = fsl_buffer_copy_lines(0, pV1, (fsl_size_t)nDel);
      if(!rc) rc = fsl_buffer_copy_lines(pOut, pV2, (fsl_size_t)nIns);
      RC;
      aC1[i1] -= nDel;
      i2 += 3;
    }else
    if( aC2[i2] >= aC1[i1+1] && aC2[i2]>0 && aC1[i1+1]+aC1[i1+2]>0 ){
      /* Output edits to V1 that occur within unchanged regions of V2 */
      nDel = aC1[i1+1];
      nIns = aC1[i1+2];
      FDEBUG( printf("EDIT -%d+%d right\n", nDel, nIns); )
      rc = fsl_buffer_copy_lines(0, pPivot, (fsl_size_t)nDel);
      if(!rc) fsl_buffer_copy_lines(0, pV2, (fsl_size_t)nDel);
      if(!rc) fsl_buffer_copy_lines(pOut, pV1, (fsl_size_t)nIns);
      aC2[i2] -= nDel;
      i1 += 3;
    }else
    if( sameEdit(&aC1[i1], &aC2[i2], pV1, pV2) ){
      /* Output edits that are identical in both V1 and V2. */
      assert( aC1[i1]==0 );
      nDel = aC1[i1+1];
      nIns = aC1[i1+2];
      FDEBUG( printf("EDIT -%d+%d both\n", nDel, nIns); )
      rc = fsl_buffer_copy_lines(0, pPivot, (fsl_size_t)nDel);
      if(!rc) fsl_buffer_copy_lines(pOut, pV1, (fsl_size_t)nIns);
      if(!rc) fsl_buffer_copy_lines(0, pV2, (fsl_size_t)nIns);
      i1 += 3;
      i2 += 3;
    }else
    {
      /* We have found a region where different edits to V1 and V2 overlap.
      ** This is a merge conflict.  Find the size of the conflict, then
      ** output both possible edits separated by distinctive marks.
      */
      int sz = 1;    /* Size of the conflict in lines */
      ++nConflict;
      while( !ends_at_CPY(&aC1[i1], sz) || !ends_at_CPY(&aC2[i2], sz) ){
        ++sz;
      }
      FDEBUG( printf("CONFLICT %d\n", sz); )
      rc = ensure_line_end(pOut, useCrLf);
      if(!rc) rc = fsl_buffer_append(pOut, mergeMarker[0], mmLen);
      if(!rc) rc = ensure_line_end(pOut, useCrLf);
      RC;
      rc = output_one_side(pOut, pV1, aC1, i1, sz, &i1);
      if(!rc) rc = ensure_line_end(pOut, useCrLf);
      RC;
      rc = fsl_buffer_append(pOut, mergeMarker[1], mmLen);
      if(!rc) rc = ensure_line_end(pOut, useCrLf);
      if(!rc) rc = fsl_buffer_copy_lines(pOut, pPivot, sz);
      if(!rc) rc = ensure_line_end(pOut, useCrLf);
      RC;
      rc = fsl_buffer_append(pOut, mergeMarker[2], mmLen);
      if(!rc) rc = ensure_line_end(pOut, useCrLf);
      if(!rc) rc = output_one_side(pOut, pV2, aC2, i2, sz, &i2);
      if(!rc) rc = ensure_line_end(pOut, useCrLf);
      RC;
      rc = fsl_buffer_append(pOut, mergeMarker[3], mmLen);
      if(!rc) rc = ensure_line_end(pOut, useCrLf);
      RC;
   }

    /* If we are finished with an edit triple, advance to the next
    ** triple.
    */
    if( i1<limit1 && aC1[i1]==0 && aC1[i1+1]==0 && aC1[i1+2]==0 ) i1+=3;
    if( i2<limit2 && aC2[i2]==0 && aC2[i2+1]==0 && aC2[i2+2]==0 ) i2+=3;
  }

  /* When one of the two edit vectors reaches its end, there might still
  ** be an insert in the other edit vector.  Output this remaining
  ** insert.
  */
  FDEBUG( printf("%d: %2d %2d %2d   %d: %2d %2d %2d\n",
         i1/3, aC1[i1], aC1[i1+1], aC1[i1+2],
         i2/3, aC2[i2], aC2[i2+1], aC2[i2+2]); )
  if( i1<limit1 && aC1[i1+2]>0 ){
    FDEBUG( printf("INSERT +%d left\n", aC1[i1+2]); )
    rc = fsl_buffer_copy_lines(pOut, pV1, aC1[i1+2]);
  }else if( i2<limit2 && aC2[i2+2]>0 ){
    FDEBUG( printf("INSERT +%d right\n", aC2[i2+2]); )
    rc = fsl_buffer_copy_lines(pOut, pV2, aC2[i2+2]);
  }

  end:
  fsl_free(aC1);
  fsl_free(aC2);
  if(!rc && conflictCount) *conflictCount = nConflict;
  return rc;
#undef RC
}

/*
** Return true if the input string contains a merge marker on a line by
** itself.
*/
bool fsl_buffer_contains_merge_marker(fsl_buffer const *p){
  fsl_size_t i;
  fsl_size_t const len = (fsl_size_t)assert_mema_lengths();
  if(p->used <= len) return false;
  fsl_size_t j;
  const char * const z = (const char *)p->mem;
  fsl_size_t const n = p->used - len + 1;
  for(i=0; i<n; ){
    for(j=0; j<4; ++j){
      if( (memcmp(&z[i], mergeMarker[j], len)==0)
          && (i+1==n || z[i+len]=='\n' || z[i+len]=='\r') ) return true;
    }
    while( i<n && z[i]!='\n' ){ ++i; }
    while( i<n && (z[i]=='\n' || z[i]=='\r') ){ ++i; }
  }
  return false;
}

#undef mymin
#undef FDEBUG
#undef ISFDEBUG
#undef MARKER
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/popen.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
** Copyright (c) 2010 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains an implementation of a bi-directional popen().
*/
/*************************************************************************
  This copy has been modified slightly for use in the libfossil project.
*/
#undef __STRICT_ANSI__
#include "fossil-scm/fossil-util.h" /* MUST come first b/c of config macros */
#include "fossil-scm/fossil-core.h"
#include <errno.h>

#ifdef _WIN32
#include <windows.h>
#include <fcntl.h>
/*
   Print a fatal error and quit.
*/
static void win32_fatal_error(const char *zMsg){
  /*fossil_fatal("%s", zMsg); TODO: what to do here? */
}
#else
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#endif

/*
   The following macros are used to cast pointers to integers and
   integers to pointers.  The way you do this varies from one compiler
   to the next, so we have developed the following set of #if statements
   to generate appropriate macros for a wide range of compilers.
  
   The correct "ANSI" way to do this is to use the intptr_t type. 
   Unfortunately, that typedef is not available on all compilers, or
   if it is available, it requires an #include of specific headers
   that vary from one machine to the next.
  
   This code is copied out of SQLite.
*/
#if defined(__PTRDIFF_TYPE__)  /* This case should work for GCC */
# define INT_TO_PTR(X)  ((void*)(__PTRDIFF_TYPE__)(X))
# define PTR_TO_INT(X)  ((int)(__PTRDIFF_TYPE__)(X))
#elif !defined(__GNUC__)       /* Works for compilers other than LLVM */
# define INT_TO_PTR(X)  ((void*)&((char*)0)[X])
# define PTR_TO_INT(X)  ((int)(((char*)X)-(char*)0))
#elif defined(HAVE_STDINT_H)   /* Use this case if we have ANSI headers */
# define INT_TO_PTR(X)  ((void*)(intptr_t)(X))
# define PTR_TO_INT(X)  ((int)(intptr_t)(X))
#else                          /* Generates a warning - but it always works */
# define INT_TO_PTR(X)  ((void*)(X))
# define PTR_TO_INT(X)  ((int)(X))
#endif


#ifdef _WIN32
/*
   On windows, create a child process and specify the stdin, stdout,
   and stderr channels for that process to use.
  
   Return the number of errors.
*/
static int win32_create_child_process(
  wchar_t *zCmd,       /* The command that the child process will run */
  HANDLE hIn,          /* Standard input */
  HANDLE hOut,         /* Standard output */
  HANDLE hErr,         /* Standard error */
  DWORD *pChildPid     /* OUT: Child process handle */
){
  STARTUPINFOW si;
  PROCESS_INFORMATION pi;
  BOOL rc;

  memset(&si, 0, sizeof(si));
  si.cb = sizeof(si);
  si.dwFlags = STARTF_USESTDHANDLES;
  SetHandleInformation(hIn, HANDLE_FLAG_INHERIT, TRUE);
  si.hStdInput  = hIn;
  SetHandleInformation(hOut, HANDLE_FLAG_INHERIT, TRUE);
  si.hStdOutput = hOut;
  SetHandleInformation(hErr, HANDLE_FLAG_INHERIT, TRUE);
  si.hStdError  = hErr;
  rc = CreateProcessW(
     NULL,  /* Application Name */
     zCmd,  /* Command-line */
     NULL,  /* Process attributes */
     NULL,  /* Thread attributes */
     TRUE,  /* Inherit Handles */
     0,     /* Create flags  */
     NULL,  /* Environment */
     NULL,  /* Current directory */
     &si,   /* Startup Info */
     &pi    /* Process Info */
  );
  if( rc ){
    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );
    *pChildPid = pi.dwProcessId;
  }else{
    win32_fatal_error("cannot create child process");
  }
  return rc!=0;
}
#endif

/**
    Create a child process running shell command "zCmd".  *ppOut gets
    assigned to a FILE that becomes the standard input of the child process.  
    (The caller writes to *ppOut in order to send text to the child.)
    *pfdIn gets assigned to the stdout from the child process.  (The caller
    reads from *pfdIn in order to receive input from the child.)
    Note that *pfdIn is an unbuffered file descriptor, not a FILE.
    The process ID of the child is written into *pChildPid.
   
    On success the values returned via *pfdIn, *ppOut, and *pChildPid
    must be passed to fsl_pclose2() to properly clean up.
   
    Return 0 on success, non-0 on error.
 */
int fsl_popen2(const char *zCmd, int *pfdIn, FILE **ppOut, int *pChildPid){
#ifdef _WIN32
  /* FIXME: port these win32_fatal_error() bits to error codes. */
  HANDLE hStdinRd, hStdinWr, hStdoutRd, hStdoutWr, hStderr;
  SECURITY_ATTRIBUTES saAttr;    
  DWORD childPid = 0;
  int fd;

  saAttr.nLength = sizeof(saAttr);
  saAttr.bInheritHandle = TRUE;
  saAttr.lpSecurityDescriptor = NULL; 
  hStderr = GetStdHandle(STD_ERROR_HANDLE);
  if( !CreatePipe(&hStdoutRd, &hStdoutWr, &saAttr, 4096) ){
    win32_fatal_error("cannot create pipe for stdout");
  }
  SetHandleInformation( hStdoutRd, HANDLE_FLAG_INHERIT, FALSE);

  if( !CreatePipe(&hStdinRd, &hStdinWr, &saAttr, 4096) ){
    win32_fatal_error("cannot create pipe for stdin");
  }
  SetHandleInformation( hStdinWr, HANDLE_FLAG_INHERIT, FALSE);
  
  win32_create_child_process(fsl_utf8_to_unicode(zCmd),
                             hStdinRd, hStdoutWr, hStderr,&childPid);
  *pChildPid = childPid;
  *pfdIn = _open_osfhandle(PTR_TO_INT(hStdoutRd), 0);
  fd = _open_osfhandle(PTR_TO_INT(hStdinWr), 0);
  *ppOut = _fdopen(fd, "w");
  CloseHandle(hStdinRd); 
  CloseHandle(hStdoutWr);
  return 0;
#else
  int rc;
  int pin[2], pout[2];
  *pfdIn = 0;
  *ppOut = 0;
  *pChildPid = 0;

  if( pipe(pin)<0 ){
    return fsl_errno_to_rc(errno, FSL_RC_ERROR);
  }
  if( pipe(pout)<0 ){
    rc = fsl_errno_to_rc(errno, FSL_RC_ERROR);
    close(pin[0]);
    close(pin[1]);
    return rc;
  }
  *pChildPid = fork();
  if( *pChildPid<0 ){
    rc = fsl_errno_to_rc(errno, FSL_RC_ERROR);
    close(pin[0]);
    close(pin[1]);
    close(pout[0]);
    close(pout[1]);
    *pChildPid = 0;
    return rc;
  }
  signal(SIGPIPE,SIG_IGN);
  if( *pChildPid==0 ){
    int fd;
    int nErr = 0;
    /* This is the child process */
    close(0);
    fd = dup(pout[0]);
    if( fd!=0 ) nErr++;
    close(pout[0]);
    close(pout[1]);
    close(1);
    fd = dup(pin[1]);
    if( fd!=1 ) nErr++;
    close(pin[0]);
    close(pin[1]);
    execl("/bin/sh", "/bin/sh", "-c", zCmd, (char*)0);
    return fsl_errno_to_rc(errno, FSL_RC_ERROR);
  }else{
    /* This is the parent process */
    close(pin[1]);
    *pfdIn = pin[0];
    close(pout[0]);
    *ppOut = fdopen(pout[1], "w");
    return 0;
  }
#endif
}

/**
    Close the connection to a child process previously created using
    fsl_popen2().  Kill off the child process, then close the pipes.
 */
void fsl_pclose2(int fdIn, FILE *pOut, int childPid){
#ifdef _WIN32
  /* Not implemented, yet */
  close(fdIn);
  fclose(pOut);
#else
  close(fdIn);
  fclose(pOut);
  kill(childPid, SIGINT);
  while( waitpid(0, 0, WNOHANG)>0 ) {}
#endif
}

#undef PTR_TO_INT
#undef INT_TO_PTR
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
































































































































































































































































































































































































































































































Deleted src/pq.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 houses the priority queue class.
*/
#include <assert.h>

#include "fossil-scm/fossil.h"
#include "fossil-scm/fossil-internal.h"

void fsl_pq_clear(fsl_pq *p){
  fsl_free(p->list);
  *p = fsl_pq_empty;
}

/*
   Change the size of the queue so that it contains N slots
*/
static int fsl_pq_resize(fsl_pq *p, fsl_size_t N){
  void * re = fsl_realloc(p->list, sizeof(fsl_pq_entry)*N);
  if(!re) return FSL_RC_OOM;
  else{
    p->list = (fsl_pq_entry*)re;
    p->capacity = N;
    return 0;
  }
}

/**
   Insert element e into the queue.
*/
int fsl_pq_insert(fsl_pq *p, fsl_id_t e,
                  double v, void *pData){
  fsl_size_t i, j;
  if( p->used+1>p->capacity ){
    int const rc = fsl_pq_resize(p, p->used+5);
    if(rc) return rc;
  }
  for(i=0; i<p->used; ++i){
    if( p->list[i].priority>v ){
      for(j=p->used; j>i; --j){
        p->list[j] = p->list[j-1];
      }
      break;
    }
  }
  p->list[i].id = e;
  p->list[i].data = pData;
  p->list[i].priority = v;
  ++p->used;
  return 0;
}

fsl_id_t fsl_pq_extract(fsl_pq *p, void **pp){
  fsl_id_t e, i;
  if( p->used==0 ){
    if( pp ) *pp = 0;
    return 0;
  }
  e = p->list[0].id;
  if( pp ) *pp = p->list[0].data;
  for(i=0; i<((fsl_id_t)p->used-1); ++i){
    p->list[i] = p->list[i+1];
  }
  --p->used;
  return e;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


























































































































































Deleted src/repo.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 implements most of the fsl_repo_xxx() APIs.
*/
#include "fossil-scm/fossil-internal.h"
#include "fossil-scm/fossil-repo.h"
#include "fossil-scm/fossil-checkout.h"
#include "fossil-scm/fossil-hash.h"
#include "fossil-scm/fossil-confdb.h"
#include <assert.h>
#include <memory.h> /* memcpy() */
#include <time.h> /* time() */
#include <errno.h>

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

/**
   Calculate the youngest ancestor of the given blob.rid value that is a member of
   branch zBranch.

   Returns the blob.id value of the matching record, 0 if not found,
   or a negative value on error.

   Potential TODO: do we need this in the public API?
*/
static fsl_id_t fsl_youngest_ancestor_in_branch(fsl_cx * f, fsl_id_t rid,
                                                const char *zBranch){
  fsl_db * const db = fsl_needs_repo(f);
  if(!db) return (fsl_id_t)-1;
  return fsl_db_g_id(db, 0,
    "WITH RECURSIVE "
    "  ancestor(rid, mtime) AS ("
    "    SELECT %"FSL_ID_T_PFMT", "
    "      mtime FROM event WHERE objid=%"FSL_ID_T_PFMT
    "    UNION "
    "    SELECT plink.pid, event.mtime"
    "      FROM ancestor, plink, event"
    "     WHERE plink.cid=ancestor.rid"
    "       AND event.objid=plink.pid"
    "     ORDER BY mtime DESC"
    "  )"
    "  SELECT ancestor.rid FROM ancestor"
    "   WHERE EXISTS(SELECT 1 FROM tagxref"
                    " WHERE tagid=%d AND tagxref.rid=ancestor.rid"
                    "   AND value=%Q AND tagtype>0)"
    "  LIMIT 1",
    rid, rid, FSL_TAGID_BRANCH, zBranch
  );
}

/**
   TODO: figure out if this needs to be in the public API and, if it does,
   change its signature to:

   int fsl_branch_of_rid(fsl_cx *f, fsl_int_t rid, char **zOut )

   So that we can distinguish "not found" from OOM errors.
*/
static char * fsl_branch_of_rid(fsl_cx *f, fsl_int_t rid){
  char *zBr = 0;
  fsl_db * const db = fsl_cx_db_repo(f);
  fsl_stmt * st = 0;
  int rc;
  assert(db);
  rc = fsl_db_prepare_cached(db, &st,
      "SELECT value FROM tagxref "
      "WHERE rid=? AND tagid=%d "
      "AND tagtype>0 "
      "/*%s()*/", FSL_TAGID_BRANCH,__func__);
  if(rc) return 0;
  rc = fsl_stmt_bind_id(st, 1, rid);
  if(rc) goto end;
  if( fsl_stmt_step(st)==FSL_RC_STEP_ROW ){
    zBr = fsl_strdup(fsl_stmt_g_text(st,0,0));
    if(!zBr) rc = FSL_RC_OOM;
  }
  end:
  fsl_stmt_cached_yield(st);
  if( !rc && zBr==0 ){
    zBr = fsl_config_get_text(f, FSL_CONFDB_REPO, "main-branch", 0);
  }
  return zBr;
}

/**
   morewt ==> most recent event with tag

   Comments from original fossil implementation:

   Find the RID of the most recent object with symbolic tag zTag and
   having a type that matches zType.

   Return 0 if there are no matches.

   This is a tricky query to do efficiently.  If the tag is very
   common (ex: "trunk") then we want to use the query identified below
   as Q1 - which searching the most recent EVENT table entries for the
   most recent with the tag.  But if the tag is relatively scarce
   (anything other than "trunk", basically) then we want to do the
   indexed search show below as Q2.
*/
static fsl_id_t fsl_morewt(fsl_cx * const f, const char *zTag, fsl_satype_e type){
  char const * zType = fsl_satype_event_cstr(type);
  return fsl_db_g_id(fsl_cx_db_repo(f), 0,
    "SELECT objid FROM ("
      /* Q1:  Begin by looking for the tag in the 30 most recent events */
      "SELECT objid"
       " FROM (SELECT * FROM event ORDER BY mtime DESC LIMIT 30) AS ex"
      " WHERE type GLOB '%q'"
        " AND EXISTS(SELECT 1 FROM tagxref, tag"
                     " WHERE tag.tagname='sym-%q'"
                       " AND tagxref.tagid=tag.tagid"
                       " AND tagxref.tagtype>0"
                       " AND tagxref.rid=ex.objid)"
      " ORDER BY mtime DESC LIMIT 1"
    ") UNION ALL SELECT * FROM ("
      /* Q2: If the tag is not found in the 30 most recent events, then using
      ** the tagxref table to index for the tag */
      "SELECT event.objid"
       " FROM tag, tagxref, event"
      " WHERE tag.tagname='sym-%q'"
        " AND tagxref.tagid=tag.tagid"
        " AND tagxref.tagtype>0"
        " AND event.objid=tagxref.rid"
        " AND event.type GLOB '%q'"
      " ORDER BY event.mtime DESC LIMIT 1"
    ") LIMIT 1;",
    zType, zTag, zTag, zType
  );
}

/**
   Modes for fsl_start_of_branch().
*/
enum fsl_stobr_type {
/**
   The check-in of the parent branch off of which
   the branch containing RID originally diverged.
*/
FSL_STOBR_ORIGIN = 0,
/**
   The first check-in of the branch that contains RID.
*/
FSL_STOBR_FIRST_CI = 1,
/**
   The youngest ancestor of RID that is on the branch from which the
   branch containing RID diverged.
*/
FSL_STOBR_YOAN = 2
};

/*
** Return the RID that is the "root" of the branch that contains
** check-in "rid".  Details depending on eType. If not found, rid is
** returned.
*/
static fsl_id_t fsl_start_of_branch(fsl_cx * f, fsl_id_t rid,
                                    enum fsl_stobr_type eType){
  fsl_db * db;
  fsl_stmt q = fsl_stmt_empty;
  int rc;
  fsl_id_t ans = rid;
  char *zBr = fsl_branch_of_rid(f, rid);
  if(!zBr){
    goto oom;
  }
  db = fsl_cx_db_repo(f);
  assert(db);
  rc = fsl_db_prepare(db, &q,
    "SELECT pid, EXISTS(SELECT 1 FROM tagxref"
                       " WHERE tagid=%d AND tagtype>0"
                       "   AND value=%Q AND rid=plink.pid)"
    "  FROM plink"
    " WHERE cid=? AND isprim",
    FSL_TAGID_BRANCH, zBr
  );
  fsl_free(zBr);
  zBr = 0;
  if(rc){
    ans = -2;
    fsl_cx_uplift_db_error(f, db);
    MARKER(("Internal error: fsl_db_prepare() says: %s\n", fsl_rc_cstr(rc)));
    goto end;
  }
  do{
    fsl_stmt_reset(&q);
    fsl_stmt_bind_id(&q, 1, ans);
    rc = fsl_stmt_step(&q);
    if( rc!=FSL_RC_STEP_ROW ) break;
    if( eType==FSL_STOBR_FIRST_CI && fsl_stmt_g_int32(&q,1)==0 ){
      break;
    }
    ans = fsl_stmt_g_id(&q, 0);
  }while( fsl_stmt_g_int32(&q, 1)==1 && ans>0 );
  fsl_stmt_finalize(&q);
  end:
  if( ans>0 && eType==FSL_STOBR_YOAN ){
    zBr = fsl_branch_of_rid(f, ans);
    if(zBr){
      ans = fsl_youngest_ancestor_in_branch(f, rid, zBr);
      fsl_free(zBr);
    }else{
      goto oom;
    }
  }
  return ans;
  oom:
  fsl_cx_err_set(f, FSL_RC_OOM, NULL);
  return -1;
}

int fsl_sym_to_rid( fsl_cx * f, char const * sym, fsl_satype_e type,
                    fsl_id_t * rv ){
  fsl_id_t rid = 0;
  fsl_id_t vid;
  fsl_size_t symLen;
  /* fsl_int_t i; */
  fsl_db * dbR = fsl_cx_db_repo(f);
  fsl_db * dbC = fsl_cx_db_ckout(f);
  bool startOfBranch = 0;
  int rc = 0;

  if(!f || !sym || !*sym || !rv) return FSL_RC_MISUSE;
  else if(!dbR) return FSL_RC_NOT_A_REPO;

  if(FSL_SATYPE_BRANCH_START==type){
    /* The original implementation takes a (char const *) for the
       type, and treats "b" (branch?) as a special case of
       FSL_SATYPE_CHECKIN, resets the type to "ci", then sets
       startOfBranch to 1. We introduced the FSL_SATYPE_BRANCH
       pseudo-type for that purpose. That said: the original code
       base does not, as of this writing (2021-02-15) appear to actually
       use this feature anywhere. */
    type = FSL_SATYPE_CHECKIN;
    startOfBranch = 1;
  }

  /* special keyword: "tip" */
  if( 0==fsl_strcmp(sym,"tip")
      && (FSL_SATYPE_ANY==type || FSL_SATYPE_CHECKIN==type)){
    rid = fsl_db_g_id(dbR, 0,
                      "SELECT objid FROM event"
                      " WHERE type='ci'"
                      " ORDER BY event.mtime DESC"
                      " LIMIT 1");
    if(rid>0) goto gotit;
  }
  /* special keywords: "prev", "previous", "current", and "next".
     These require a checkout.
  */
  vid = dbC ? f->ckout.rid : 0;
  //MARKER(("has vid=%"FSL_ID_T_PFMT"\n", vid));
  if( vid>0){
    if( 0==fsl_strcmp(sym, "current") ){
      rid = vid;
    }
    else if( 0==fsl_strcmp(sym, "prev")
             || 0==fsl_strcmp(sym, "previous") ){
      rid = fsl_db_g_id(dbR, 0,
                        "SELECT pid FROM plink WHERE "
                        "cid=%"FSL_ID_T_PFMT" AND isprim",
                        (fsl_id_t)vid);
    }
    else if( 0==fsl_strcmp(sym, "next") ){
      rid = fsl_db_g_id(dbR, 0,
                        "SELECT cid FROM plink WHERE "
                        "pid=%"FSL_ID_T_PFMT
                        " ORDER BY isprim DESC, mtime DESC",
                        (fsl_id_t)vid);
    }
    if(rid>0) goto gotit;
  }

  /* Date and times */
  if( 0==memcmp(sym, "date:", 5) ){
    rid = fsl_db_g_id(dbR, 0, 
                      "SELECT objid FROM event"
                      " WHERE mtime<=julianday(%Q,'utc')"
                      " AND type GLOB '%q'"
                      " ORDER BY mtime DESC LIMIT 1",
                      sym+5, fsl_satype_event_cstr(type));
    *rv = rid;
    return 0;
  }
  if( fsl_str_is_date(sym) ){
    rid = fsl_db_g_id(dbR, 0, 
                      "SELECT objid FROM event"
                      " WHERE mtime<=julianday(%Q,'utc')"
                      " AND type GLOB '%q'"
                      " ORDER BY mtime DESC LIMIT 1",
                      sym, fsl_satype_event_cstr(type));
    if(rid>0) goto gotit;
  }

  /* Deprecated time formats elided: local:..., utc:... */

  /* "tag:" + symbolic-name */
  if( memcmp(sym, "tag:", 4)==0 ){
    rid = fsl_morewt(f, sym+4, type);
    if(rid>0 && startOfBranch){
      rid = fsl_start_of_branch(f, rid, FSL_STOBR_FIRST_CI);
    }
    goto gotit;
  }

  /* root:TAG -> The origin of the branch */
  if( memcmp(sym, "root:", 5)==0 ){
    rc = fsl_sym_to_rid(f, sym+5, type, &rid);
    if(!rc && rid>0){
      rid = fsl_start_of_branch(f, rid, FSL_STOBR_ORIGIN);
    }
    goto gotit;
  }  

  /* merge-in:TAG -> Most recent merge-in for the branch */
  if( memcmp(sym, "merge-in:", 9)==0 ){
    rc = fsl_sym_to_rid(f, sym+9, type, &rid);
    if(!rc){
      rid = fsl_start_of_branch(f, rid, FSL_STOBR_YOAN);
    }
    goto gotit;
  }  
  
  symLen = fsl_strlen(sym);
  /* SHA1/SHA3 hash or prefix */
  if( symLen>=4
      && symLen<=FSL_STRLEN_K256
      && fsl_validate16(sym, symLen) ){
    fsl_stmt q = fsl_stmt_empty;
    char zUuid[FSL_STRLEN_K256+1];
    memcpy(zUuid, sym, symLen);
    zUuid[symLen] = 0;
    fsl_canonical16(zUuid, symLen);
    rid = 0;
    /* Reminder to self: caching these queries would be cool but it
       can't work with the GLOBs.
    */
    if( FSL_SATYPE_ANY==type ){
      fsl_db_prepare(dbR, &q,
                       "SELECT rid FROM blob WHERE uuid GLOB '%s*'",
                       zUuid);
    }else{
      fsl_db_prepare(dbR, &q,
                     "SELECT blob.rid"
                     "  FROM blob, event"
                     " WHERE blob.uuid GLOB '%s*'"
                     "   AND event.objid=blob.rid"
                     "   AND event.type GLOB '%q'",
                     zUuid, fsl_satype_event_cstr(type) );
    }
    if( fsl_stmt_step(&q)==FSL_RC_STEP_ROW ){
      int64_t r64 = 0;
      fsl_stmt_get_int64(&q, 0, &r64);
      if( fsl_stmt_step(&q)==FSL_RC_STEP_ROW ) rid = -1
        /* Ambiguous results */
        ;
      else rid = (fsl_id_t)r64;
    }
    fsl_stmt_finalize(&q);
    if(rid<0){
      fsl_cx_err_set(f, FSL_RC_AMBIGUOUS,
                     "Symbolic name is ambiguous: %s",
                     sym);
    }
    goto gotit
      /* None of the further checks against the sym can pass. */
      ;
  }

  if(FSL_SATYPE_WIKI==type){
    rid = fsl_db_g_id(dbR, 0,
                    "SELECT event.objid, max(event.mtime)"
                    "  FROM tag, tagxref, event"
                    " WHERE tag.tagname='sym-%q' "
                    "   AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
                    "   AND event.objid=tagxref.rid "
                    "   AND event.type GLOB '%q'",
                    sym, fsl_satype_event_cstr(type)
    );
  }else{
    rid = fsl_morewt(f, sym, type);
    //MARKER(("morewt(%s,%s) == %d\n", sym, fsl_satype_cstr(type), (int)rid));
  }

  if( rid>0 ){
    if(startOfBranch) rid = fsl_start_of_branch(f, rid,
                                                FSL_STOBR_FIRST_CI);
    goto gotit;
  }

  /* Undocumented: rid:### ==> rid */
  if(symLen>4 && 0==fsl_strncmp("rid:",sym,4)){
    int i;
    char const * oldSym = sym;
    sym += 4;
    for(i=0; fsl_isdigit(sym[i]); i++){}
    if( sym[i]==0 ){
      if( FSL_SATYPE_ANY==type ){
        rid = fsl_db_g_id(dbR, 0, 
                          "SELECT rid"
                          "  FROM blob"
                          " WHERE rid=%s",
                          sym);
      }else{
        rid = fsl_db_g_id(dbR, 0, 
                          "SELECT event.objid"
                          "  FROM event"
                          " WHERE event.objid=%s"
                          "   AND event.type GLOB '%q'",
                          sym, fsl_satype_event_cstr(type));
      }
      if( rid>0 ) goto gotit;
    }
    sym = oldSym;
  }

  gotit:
  if(rid<=0){
    return f->error.code
      ? f->error.code
      : fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                       "Could not resolve symbolic name "
                       "'%s' as artifact type '%s'.",
                       sym, fsl_satype_event_cstr(type) );
  }
  assert(0==rc);
  *rv = rid;
  return rc;
}

fsl_id_t fsl_uuid_to_rid2( fsl_cx * f, fsl_uuid_cstr uuid,
                           fsl_phantom_e mode ){
    if(!f) return -1;
    else if(!fsl_is_uuid(uuid)){
      fsl_cx_err_set(f, FSL_RC_MISUSE,
                     "fsl_uuid_to_rid2() requires a "
                     "full UUID. Got: %s", uuid);
      return -2;
    }else{
      fsl_id_t rv;
      rv = fsl_uuid_to_rid(f, uuid);
      if((0==rv) && (FSL_PHANTOM_NONE!=mode)
         && 0!=fsl_content_new(f, uuid,
                               (FSL_PHANTOM_PRIVATE==mode),
                               &rv)){
        assert(f->error.code);
        rv = -3;
      }
      return rv;
    }
}

int fsl_sym_to_uuid( fsl_cx * f, char const * sym, fsl_satype_e type,
                     fsl_uuid_str * rv, fsl_id_t * rvId ){
  fsl_id_t rid = 0;
  fsl_db * dbR = fsl_needs_repo(f);
  fsl_uuid_str rvv = NULL;
  int rc = dbR
    ? fsl_sym_to_rid(f, sym, type, &rid)
    : FSL_RC_NOT_A_REPO;
  if(!rc){
    if(rvId) *rvId = rid;
    rvv = fsl_rid_to_uuid(f, rid)
      /* TODO: use a cached "exists" check if !rv, to avoid allocating
         rvv if we don't need it.
      */;
    if(!rvv){
      if(!f->error.code){
        rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                            "Cannot find UUID for RID %"FSL_ID_T_PFMT".",
                            rid);
      }
    }
    else if(rv){
      *rv = rvv;
    }else{
      fsl_free( rvv );
    }
  }
  return rc;
}

fsl_id_t fsl_uuid_to_rid( fsl_cx * f, char const * uuid ){
  fsl_db * const db = fsl_needs_repo(f);
  fsl_size_t const uuidLen = (uuid && db) ? fsl_strlen(uuid) : 0;
  if(!f || !uuid || !uuidLen) return -1;
  else if(!db){
    /* f's error state has already been set */
    return -2;
  }
  else if(!fsl_validate16(uuid, uuidLen)){
    fsl_cx_err_set(f, FSL_RC_RANGE, "Invalid UUID (prefix): %s", uuid);
    return -3;
  }
  else if(uuidLen>FSL_STRLEN_K256){
    fsl_cx_err_set(f, FSL_RC_RANGE, "UUID is too long: %s", uuid);
    return -4;
  }
  else {
    fsl_id_t rid = -5;
    fsl_stmt q = fsl_stmt_empty;
    fsl_stmt * qS = NULL;
    int rc;
    rc = fsl_is_uuid_len((int)uuidLen)
      /* Optimization for the common internally-used case.

         FIXME: there is an *astronomically small* chance of a prefix
         collision on a v1-length uuidLen against a v2-length
         blob.uuid value, leading to no match found for an existing v2
         uuid here. Like... a *REALLY* small chance.
      */
      ? fsl_db_prepare_cached(db, &qS,
                              "SELECT rid FROM blob WHERE "
                              "uuid=? /*%s()*/",__func__)
      : fsl_db_prepare(db, &q,
                       "SELECT rid FROM blob WHERE "
                       "uuid GLOB '%s*'",
                       uuid);
    if(!rc){
      fsl_stmt * st = qS ? qS : &q;
      if(qS){
        rc = fsl_stmt_bind_text(qS, 1, uuid, (fsl_int_t)uuidLen, 0);
      }
      if(!rc){
        rc = fsl_stmt_step(st);
        switch(rc){
          case FSL_RC_STEP_ROW:
            rc = 0;
            rid = fsl_stmt_g_id(st, 0);
            if(!qS){
              /*
                Check for an ambiguous result. We don't need this for
                the (qS==st) case because that one does an exact match
                on a unique key.
              */
              rc = fsl_stmt_step(st);
              switch(rc){
                case FSL_RC_STEP_ROW:
                  rc = 0;
                  fsl_cx_err_set(f, FSL_RC_AMBIGUOUS,
                                 "UUID prefix is ambiguous: %s",
                                 uuid);
                  rid = -6;
                break;
                case FSL_RC_STEP_DONE:
                  /* Unambiguous UUID */
                  rc = 0;
                  break;
                default:
                  assert(st->db->error.code);
                  /* fall through and uplift the db error below... */
              }
            }
            break;
          case FSL_RC_STEP_DONE:
            /* No entry found */
            rid = 0;
            rc = 0;
            break;
          default:
            assert(st->db->error.code);
            rid = -7;
            break;
        }
      }
      if(rc && db->error.code && !f->error.code){
        fsl_cx_uplift_db_error(f, db);
      }
      if(qS) fsl_stmt_cached_yield(qS);
      else fsl_stmt_finalize(&q);
    }
    return rid;
  }
}

fsl_id_t fsl_repo_filename_fnid( fsl_cx * f, char const * fn ){
  fsl_id_t rv = 0;
  int const rc = fsl_repo_filename_fnid2(f, fn, &rv, false);
  return rv>=0 ? rv : (rc>0 ? -rc : rc);
}

int fsl_repo_filename_fnid2( fsl_cx * f, char const * fn, fsl_id_t * rv, bool createNew ){
  fsl_db * db = fsl_cx_db_repo(f);
  fsl_id_t fnid = 0;
  fsl_stmt * qSel = NULL;
  int rc;
  assert(f);
  assert(db);
  assert(rv);
  if(!fn || !fsl_is_simple_pathname(fn, 1)){
    return fsl_cx_err_set(f, FSL_RC_RANGE,
                          "Filename is not a \"simple\" path: %s",
                          fn);
  }
  *rv = 0;
  rc = fsl_db_prepare_cached(db, &qSel,
                             "SELECT fnid FROM filename "
                             "WHERE name=? "
                             "/*%s()*/",__func__);
  if(rc){
      fsl_cx_uplift_db_error(f, db);
      return rc;
  }
  rc = fsl_stmt_bind_text(qSel, 1, fn, -1, 0);
  if(rc){
    fsl_stmt_cached_yield(qSel);
  }else{
    rc = fsl_stmt_step(qSel);
    if( FSL_RC_STEP_ROW == rc ){
      rc = 0;
      fnid = fsl_stmt_g_id(qSel, 0);
      assert(fnid>0);
    }else if(FSL_RC_STEP_DONE == rc){
      rc = 0;
    }
    fsl_stmt_cached_yield(qSel);
    if(!rc && (fnid==0) && createNew){
      fsl_stmt * qIns = NULL;
      rc = fsl_db_prepare_cached(db, &qIns,
                                 "INSERT INTO filename(name) "
                                 "VALUES(?) /*%s()*/",__func__);
      if(!rc){
        rc = fsl_stmt_bind_text(qIns, 1, fn, -1, 0);
        if(!rc){
          rc = fsl_stmt_step(qIns);
          if(FSL_RC_STEP_DONE==rc){
            rc = 0;
            fnid = fsl_db_last_insert_id(db);
          }
        }
        fsl_stmt_cached_yield(qIns);
      }
    }
  }
  if(!rc){
    assert(!createNew || (fnid>0));
    *rv = fnid;
  }else if(db->error.code){
    fsl_cx_uplift_db_error(f, db);
  }
  return rc;
}

int fsl_delta_src_id( fsl_cx * f, fsl_id_t deltaRid, fsl_id_t * rv ){
  fsl_db * const dbR = fsl_cx_db_repo(f);
  if(!rv) return FSL_RC_MISUSE;
  else if(deltaRid<=0) return FSL_RC_RANGE;
  else if(!dbR) return FSL_RC_NOT_A_REPO;
  else {
    int rc;
    fsl_stmt * q = NULL;
    rc = fsl_db_prepare_cached(dbR, &q,
                               "SELECT srcid FROM delta "
                               "WHERE rid=? /*%s()*/",__func__);
    if(!rc){
      rc = fsl_stmt_bind_id(q, 1, deltaRid);
      if(!rc){
        if(FSL_RC_STEP_ROW==(rc=fsl_stmt_step(q))){
          rc = 0;
          *rv = fsl_stmt_g_id(q, 0);
        }else if(FSL_RC_STEP_DONE==rc){
          rc = 0;
          *rv = 0;
        }
      }
      fsl_stmt_cached_yield(q);
    }
    return rc;
  }
}



int fsl_repo_verify_before_commit( fsl_cx * f, fsl_id_t rid ){
  if(0){
    /*
       v1 adds a commit hook here on the first entry, but it only
       seems to ever use one commit hook, so the infrastructure seems
       like overkill here. Thus this final verification is called from
       the commit (that's where v1 calls the hook).

       If we eventually add commit hooks, this is the place to do it.
    */
  }
  assert( fsl_cx_db_repo(f)->beginCount > 0 );
  return rid>0
    ? fsl_id_bag_insert(&f->cache.toVerify, rid)
    : FSL_RC_RANGE;    
}

void fsl_repo_verify_cancel( fsl_cx * f ){
  fsl_id_bag_clear(&f->cache.toVerify);
}

int fsl_rid_to_uuid2(fsl_cx * f, fsl_id_t rid, fsl_buffer *uuid){
  fsl_db * db = f ? fsl_cx_db_repo(f) : NULL;
  if(!f || !db || (rid<=0)){
    return fsl_cx_err_set(f, FSL_RC_MISUSE,
                          "fsl_rid_to_uuid2() requires "
                          "an opened repository and a "
                          "positive RID value. rid=%" FSL_ID_T_PFMT,
                          rid);
  }else{
    fsl_stmt * st = NULL;
    int rc;
    fsl_buffer_reuse(uuid);
    rc = fsl_db_prepare_cached(db, &st,
                               "SELECT uuid FROM blob "
                               "WHERE rid=? "
                               "/*%s()*/", __func__);
    if(!rc){
      rc = fsl_stmt_bind_id(st, 1, rid);
      if(!rc){
        rc = fsl_stmt_step(st);
        if(FSL_RC_STEP_ROW==rc){
          fsl_size_t len = 0;
          char const * x = fsl_stmt_g_text(st, 0, &len);
          rc = fsl_buffer_append(uuid, x, (fsl_int_t)len);
        }else if(FSL_RC_STEP_DONE){
          rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                              "No blob found for rid %" FSL_ID_T_PFMT ".",
                              rid);
        }
      }
      fsl_stmt_cached_yield(st);
      if(rc && !f->error.code){
        assert(db->error.code);
        fsl_cx_uplift_db_error(f, db);
      }
    }
    return rc;
  }
}

fsl_uuid_str fsl_rid_to_uuid(fsl_cx * f, fsl_id_t rid){
  fsl_buffer uuid = fsl_buffer_empty;
  fsl_rid_to_uuid2(f, rid, &uuid);
  return fsl_buffer_take(&uuid);
}

fsl_uuid_str fsl_rid_to_artifact_uuid(fsl_cx * f, fsl_id_t rid, fsl_satype_e type){
  fsl_db * db = f ? fsl_cx_db_repo(f) : NULL;
  if(!f || !db || (rid<=0)) return NULL;
  else{
    char * rv = NULL;
    fsl_stmt * st = NULL;
    int rc;
    rc = fsl_db_prepare_cached(db, &st,
                               "SELECT uuid FROM blob "
                               "WHERE rid=?1 AND EXISTS "
                               "(SELECT 1 FROM event"
                               " WHERE event.objid=?1 "
                               " AND event.type GLOB %Q)"
                               "/*%s()*/",
                               fsl_satype_event_cstr(type),
                               __func__);
    if(!rc){
      rc = fsl_stmt_bind_id(st, 1, rid);
      if(!rc){
        rc = fsl_stmt_step(st);
        if(FSL_RC_STEP_ROW==rc){
          fsl_size_t len = 0;
          char const * x = fsl_stmt_g_text(st, 0, &len);
          rv = x ? fsl_strndup(x, (fsl_int_t)len ) : NULL;
          if(x && !rv){
            fsl_cx_err_set(f, FSL_RC_OOM, NULL);
          }
        }else if(FSL_RC_STEP_DONE){
          fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                         "No %s artifact found with rid %"FSL_ID_T_PFMT".",
                         fsl_satype_cstr(type), (fsl_id_t) rid);
        }
      }
      fsl_stmt_cached_yield(st);
      if(rc && !f->error.code){
        fsl_cx_uplift_db_error(f, db);
      }
    }
    return rv;
  }
}


/**
    Load the record identified by rid. Make sure we can reproduce it
    without error.
   
    Return non-0 and set f's error state if anything goes wrong.  If
    this procedure returns 0 it means that everything looks OK.
 */
static int fsl_repo_verify_rid(fsl_cx * f, fsl_id_t rid){
  fsl_uuid_str uuid = NULL;
  fsl_buffer hash = fsl_buffer_empty;
  fsl_buffer content = fsl_buffer_empty;
  int rc;
  fsl_db * db;
  if( fsl_content_size(f, rid)<0 ){
    return 0 /* No way to verify phantoms */;
  }
  db = fsl_cx_db_repo(f);
  assert(db);
  uuid = fsl_rid_to_uuid(f, rid);
  if(!uuid){
    rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                        "Could not find blob record for "
                        "rid #%"FSL_ID_T_PFMT".",
                        rid);
  }
  else{
    int const uuidLen = fsl_is_uuid(uuid);
    if(!uuidLen){
      rc = fsl_cx_err_set(f, FSL_RC_RANGE,
                          "Invalid uuid for rid #%"FSL_ID_T_PFMT": %s",
                          (fsl_id_t)rid, uuid);
    }
    else if( 0==(rc=fsl_content_get(f, rid, &content)) ){
      /* This test can fail for artifacts which have an SHA1 hash in a
         repo with an SHA3 policy. A test case from the main fossil
         repo: c7dd1de9f9539a5a859c2b41fe4560604a774476

         This test hashes it (in that repo) as SHA3. As a workaround,
         if the hash is an SHA1 the we will temporarily force the hash
         policy to SHA1, and similarly for SHA3. Lame, but nothing
         better currently comes to mind.

         TODO: change the signature of fsl_cx_hash_buffer() to
         optionally take a forced policy, or supply a similar function
         which does what we're doing below.
      */
      fsl_hashpolicy_e const oldHashP = f->cxConfig.hashPolicy;
      f->cxConfig.hashPolicy = (uuidLen==FSL_STRLEN_SHA1)
        ? FSL_HPOLICY_SHA1 : FSL_HPOLICY_SHA3;
      rc = fsl_cx_hash_buffer(f, 0, &content, &hash);
      f->cxConfig.hashPolicy = oldHashP;
      if( !rc && 0!=fsl_uuidcmp(uuid, fsl_buffer_cstr(&hash)) ){
        rc = fsl_cx_err_set(f, FSL_RC_CONSISTENCY,
                            "Hash of rid %"FSL_ID_T_PFMT" (%b) "
                            "does not match its uuid (%s)",
                            (fsl_id_t)rid, &hash, uuid);
      }
    }
  }
  fsl_free(uuid);
  fsl_buffer_clear(&hash);
  fsl_buffer_clear(&content);
  return rc;
}


int fsl_repo_verify_at_commit( fsl_cx * f ){
  fsl_id_t rid;
  int rc = 0;
  fsl_id_bag * bag = &f->cache.toVerify;
  /* v1 does content_cache_clear() here. */
  f->cache.inFinalVerify = 1;
  rid = fsl_id_bag_first(bag);
  if(f->cxConfig.traceSql){
    fsl_db_exec(f->dbMain,
                "SELECT 'Starting verify-at-commit.'");
  }
  while( !rc && rid>0 ){
    rc = fsl_repo_verify_rid(f, rid);
    if(!rc) rid = fsl_id_bag_next(bag, rid);
  }
  fsl_id_bag_clear(bag);
  f->cache.inFinalVerify = 0;
  if(rc && !f->error.code){
    fsl_cx_err_set(f, rc,
                   "Error #%d (%s) in fsl_repo_verify_at_commit()",
                   rc, fsl_rc_cstr(rc));
  }
  return rc;
}


static int fsl_repo_create_default_users(fsl_db * db, char addOnlyUser,
                                         char const * defaultUser ){
  int rc = fsl_db_exec(db,
                       "INSERT OR IGNORE INTO user(login, info) "
                       "VALUES(%Q,'')", defaultUser);
  if(!rc){
    rc = fsl_db_exec(db,
                     "UPDATE user SET cap='s', pw=lower(hex(randomblob(3)))"
                     " WHERE login=%Q", defaultUser);
    if( !rc && !addOnlyUser ){
      fsl_db_exec_multi(db,
                        "INSERT OR IGNORE INTO user(login,pw,cap,info)"
                        "   VALUES('anonymous',hex(randomblob(8)),'hmncz',"
                        "          'Anon');"
                        "INSERT OR IGNORE INTO user(login,pw,cap,info)"
                        "   VALUES('nobody','','gjor','Nobody');"
                        "INSERT OR IGNORE INTO user(login,pw,cap,info)"
                        "   VALUES('developer','','dei','Dev');"
                        "INSERT OR IGNORE INTO user(login,pw,cap,info)"
                        "   VALUES('reader','','kptw','Reader');"
                        );
    }
  }
  return rc;                       
}

int fsl_repo_create(fsl_cx * f, fsl_repo_create_opt const * opt ){
  fsl_db * db = 0;
  fsl_cx F = fsl_cx_empty /* used if !f */;
  int rc = 0;
  char const * userName = 0;
  fsl_time_t const unixNow = (fsl_time_t)time(0);
  char fileExists;
  char inTrans = 0;
  extern int fsl_cx_attach_role(fsl_cx * f, const char *zDbName, fsl_dbrole_e r)
    /* Internal routine from fsl_cx.c */;
  if(!opt || !opt->filename) return FSL_RC_MISUSE;
  fileExists = 0 == fsl_file_access(opt->filename,0);
  if(fileExists && !opt->allowOverwrite){
    return f
      ? fsl_cx_err_set(f, FSL_RC_ALREADY_EXISTS,
                       "File already exists and "
                       "allowOverwrite is false: %s",
                       opt->filename)
      : FSL_RC_ALREADY_EXISTS;
  }
  if(f){
    rc = fsl_ckout_close(f)
      /* Will fail if a transaction is active! */;
    switch(rc){
      case 0:
      case FSL_RC_NOT_FOUND:
        rc = 0;
        break;
      default:
        return rc;
    }
  }else{
    f = &F;
    rc = fsl_cx_init( &f, NULL );
    if(rc){
      fsl_cx_finalize(f);
      return rc;
    }
  }
  /* We probably should truncate/unlink the file here
     before continuing, to ensure a clean slate.
  */
  if(fileExists){
#if 0
    FILE * file = fsl_fopen(opt->filename, "w"/*truncates it*/);
    if(!file){
      rc = fsl_cx_err_set(f, fsl_errno_to_rc(errno, FSL_RC_IO),
                          "Cannot open '%s' for writing.",
                          opt->filename);
      goto end2;    
    }else{
      fsl_fclose(file);
    }
#else
    rc = fsl_file_unlink(opt->filename);
    if(rc){
      rc = fsl_cx_err_set(f, rc, "Cannot unlink existing repo file: %s",
                          opt->filename);
      goto end2;
    }
#endif
  }
  rc = fsl_cx_attach_role(f, opt->filename, FSL_DBROLE_REPO);
  if(rc){
    goto end2;
  }
  db = fsl_cx_db(f);
  if(!f->repo.user){
    f->repo.user = fsl_guess_user_name()
      /* Ignore OOM error here - we'll use 'root'
         by default (but if we're really OOM here then
         the next op will fail).
      */;
  }
  userName = opt->username;

  rc = fsl_db_transaction_begin(db);
  if(rc) goto end1;
  inTrans = 1;
  /* Install the schemas... */
  rc = fsl_db_exec_multi(db, "%s; %s; %s; %s",
                         fsl_schema_repo1(),
                         fsl_schema_repo2(),
                         fsl_schema_ticket(),
                         fsl_schema_ticket_reports());
  if(rc) goto end1;

  if(1){
    /*
      Set up server-code and project-code...

      in fossil this is optional, so we will presumably eventually
      have to make it so here as well. Not yet sure where this routine
      is used in fossil (i.e. whether the option is actually
      exercised).
    */
    rc = fsl_db_exec_multi(db,
                           "INSERT INTO repo.config (name,value,mtime) "
                           "VALUES ('server-code',"
                           "lower(hex(randomblob(20))),"
                           "%"PRIi64");"
                           "INSERT INTO repo.config (name,value,mtime) "
                           "VALUES ('project-code',"
                           "lower(hex(randomblob(20))),"
                           "%"PRIi64");",
                           (int64_t)unixNow,
                           (int64_t)unixNow
                           );
    if(rc) goto end1;
  }

  
  /* Set some config vars ... */
  {
    fsl_stmt st = fsl_stmt_empty;
    rc = fsl_db_prepare(db, &st,
                        "INSERT INTO repo.config (name,value,mtime) "
                        "VALUES (?,?,%"PRIi64")",
                        (int64_t)unixNow);
    if(!rc){
      fsl_stmt_bind_int64(&st, 3, unixNow);
#define DBSET_STR(KEY,VAL) \
      fsl_stmt_bind_text(&st, 1, KEY, -1, 0);    \
      fsl_stmt_bind_text(&st, 2, VAL, -1, 0); \
      fsl_stmt_step(&st); \
      fsl_stmt_reset(&st)
      DBSET_STR("content-schema",FSL_CONTENT_SCHEMA);
      DBSET_STR("aux-schema",FSL_AUX_SCHEMA);
#undef DBSET_STR

#define DBSET_INT(KEY,VAL) \
      fsl_stmt_bind_text(&st, 1, KEY, -1, 0 );    \
      fsl_stmt_bind_int32(&st, 2, VAL); \
      fsl_stmt_step(&st); \
      fsl_stmt_reset(&st)

      DBSET_INT("autosync",1);
      DBSET_INT("localauth",0);
      DBSET_INT("timeline-plaintext", 1);
      
#undef DBSET_INT
      fsl_stmt_finalize(&st);
    }
  }

  rc = fsl_repo_create_default_users(db, 0, userName);
  if(rc) goto end1;

  end1:
  if(db->error.code && !f->error.code){
    rc = fsl_cx_uplift_db_error(f, db);
  }
  if(inTrans){
    if(!rc) rc = fsl_db_transaction_end(db, 0);
    else fsl_db_transaction_end(db, 1);
    inTrans = 0;
  }
  fsl_cx_close_dbs(f);
  db = 0;
  if(rc) goto end2;

  /**
      In order for injection of the first commit to go through
      cleanly (==without any ugly kludging of f->dbMain), we
      need to now open the new db so that it gets connected
      to f properly...
   */
  rc = fsl_repo_open( f, opt->filename );
  if(rc) goto end2;
  db = fsl_cx_db_repo(f);
  assert(db);
  assert(db == f->dbMain);

  if(!userName || !*userName){
    userName = fsl_cx_user_get(f);
    if(!userName || !*userName){
      userName = "root" /* historical value */;
    }
  }

  /*
    Copy config...

    This is done in the second phase because...

    "cannot ATTACH database within transaction"

    and installing the initial schemas outside a transaction is
    horribly slow.
  */
  if( opt->configRepo && *opt->configRepo ){
    bool inTrans2 = false;
    char * inopConfig = fsl_config_inop_rhs(FSL_CONFIGSET_ALL);
    char * inopDb = inopConfig ? fsl_db_setting_inop_rhs() : NULL;
    if(!inopConfig || !inopDb){
      fsl_free(inopConfig);
      rc = FSL_RC_OOM;
      goto end2;
    }
    rc = fsl_db_attach(db, opt->configRepo, "settingSrc");
    if(rc){
      fsl_cx_uplift_db_error(f, db);
      goto end2;
    }
    rc = fsl_db_transaction_begin(db);
    if(rc){
      fsl_cx_uplift_db_error(f, db);
      goto detach;
    }
    inTrans2 = 1;
    /*
       Copy all settings from the supplied template repository.
    */
    rc = fsl_db_exec(db,
                     "INSERT OR REPLACE INTO repo.config"
                     " SELECT name,value,mtime FROM settingSrc.config"
                     "  WHERE (name IN %s OR name IN %s)"
                     "    AND name NOT GLOB 'project-*';",
                     inopConfig, inopDb);
    if(rc) goto detach;
    rc = fsl_db_exec(db,
                     "REPLACE INTO repo.reportfmt "
                     "SELECT * FROM settingSrc.reportfmt;");
    if(rc) goto detach;

    /*
       Copy the user permissions, contact information, last modified
       time, and photo for all the "system" users from the supplied
       template repository into the one being setup.  The other
       columns are not copied because they contain security
       information or other data specific to the other repository.
       The list of columns copied by this SQL statement may need to be
       revised in the future.
    */
    rc = fsl_db_exec(db, "UPDATE repo.user SET"
      "  cap = (SELECT u2.cap FROM settingSrc.user u2"
      "         WHERE u2.login = user.login),"
      "  info = (SELECT u2.info FROM settingSrc.user u2"
      "          WHERE u2.login = user.login),"
      "  mtime = (SELECT u2.mtime FROM settingSrc.user u2"
      "           WHERE u2.login = user.login),"
      "  photo = (SELECT u2.photo FROM settingSrc.user u2"
      "           WHERE u2.login = user.login)"
      " WHERE user.login IN ('anonymous','nobody','developer','reader');"
    );

    detach:
    fsl_free(inopConfig);
    fsl_free(inopDb);
    if(inTrans2){
      if(!rc) rc = fsl_db_transaction_end(db,0);
      else fsl_db_transaction_end(db,1);
    }
    fsl_db_detach(db, "settingSrc");
    if(rc) goto end2;
  }

  if(opt->commitMessage && *opt->commitMessage){
    /*
      Set up initial commit. Because of the historically empty P-card
      on the first commit, we can't create that one using the fsl_deck
      API unless we elide the P-card (not as fossil does) and insert
      an empty R-card (as fossil does). We need one of P- or R-card to
      unambiguously distinguish this MANIFEST from a CONTROL artifact.

      Reminder to self: fsl_deck has been adjusted to deal with the
      initial-checkin(-like) case in the mean time. But this code
      works, so no need to go changing it...
    */
    fsl_deck d = fsl_deck_empty;
    fsl_cx_err_reset(f);
    fsl_deck_init(f, &d, FSL_SATYPE_CHECKIN);
    rc = fsl_deck_C_set(&d, opt->commitMessage, -1);
    if(!rc) rc = fsl_deck_D_set(&d, fsl_db_julian_now(db));
    if(!rc) rc = fsl_deck_R_set(&d, FSL_MD5_INITIAL_HASH);
    if(!rc && opt->commitMessageMimetype && *opt->commitMessageMimetype){
      rc = fsl_deck_N_set(&d, opt->commitMessageMimetype, -1);
    }
    /* Reminder: setting tags in "wrong" (unsorted) order to
       test/assert that the sorting gets done automatically. */
    if(!rc) rc = fsl_deck_T_add(&d, FSL_TAGTYPE_PROPAGATING, NULL,
                                "sym-trunk", NULL);
    if(!rc) rc = fsl_deck_T_add(&d, FSL_TAGTYPE_PROPAGATING, NULL,
                                "branch", "trunk");
    if(!rc) rc =fsl_deck_U_set(&d, userName);
    if(!rc){
      rc = fsl_deck_save(&d, 0);
    }
    fsl_deck_finalize(&d);
  }
  
  end2:
  if(f == &F){
    fsl_cx_finalize(f);
    if(rc) fsl_file_unlink(opt->filename);
  }
  return rc;
}

static int fsl_repo_dir_names_rid( fsl_cx * f, fsl_id_t rid, fsl_list * tgt,
                                   bool addSlash){
  fsl_db * dbR = fsl_needs_repo(f);
  fsl_deck D = fsl_deck_empty;
  fsl_deck * d = &D;
  int rc = 0;
  fsl_stmt st = fsl_stmt_empty;
  fsl_buffer tname = fsl_buffer_empty;
  int count = 0;
  fsl_card_F const * fc;
  /*
    This is a poor-man's impl. A more efficient one would calculate
    the directory names without using the database.
  */
  assert(rid>0);
  assert(dbR);
  rc = fsl_deck_load_rid( f, d, rid, FSL_SATYPE_CHECKIN);
  if(rc){
    fsl_deck_clean(d);
    return rc;
  }
  rc = fsl_buffer_appendf(&tname,
                          "tmp_filelist_for_rid_%d",
                          (int)rid);
  if(rc) goto end;
  rc = fsl_deck_F_rewind(d);
  while( !rc && !(rc=fsl_deck_F_next(d, &fc)) && fc ){
    //if(!fc->name) continue;
    assert(fc->name && *fc->name);
    if(!st.stmt){
      rc = fsl_db_exec(dbR, "CREATE TEMP TABLE IF NOT EXISTS "
                       "%b(n TEXT UNIQUE ON CONFLICT IGNORE)",
                       &tname);
      if(!rc){
        rc = fsl_db_prepare(dbR, &st,
                            "INSERT INTO %b(n) "
                            "VALUES(fsl_dirpart(?,%d))",
                            &tname, addSlash ? 1 : 0);
      }
      if(rc) goto end;
      assert(st.stmt);
    }
    rc = fsl_stmt_bind_text(&st, 1, fc->name, -1, 0);
    if(!rc){
      rc = fsl_stmt_step(&st);
      if(FSL_RC_STEP_DONE==rc){
        ++count;
        rc = 0;
      }
    }
    fsl_stmt_reset(&st);
    fc = 0;
  }

  if(!rc && (count>0)){
    fsl_stmt_finalize(&st);
    rc = fsl_db_prepare(dbR, &st,
                        "SELECT n FROM %b WHERE n "
                        "IS NOT NULL ORDER BY n %s",
                        &tname,
                        fsl_cx_filename_collation(f));
    while( !rc && (FSL_RC_STEP_ROW==(rc=fsl_stmt_step(&st))) ){
      fsl_size_t nLen = 0;
      char const * name = fsl_stmt_g_text(&st, 0, &nLen);
      rc = 0;
      if(name){
        char * cp;
        assert(nLen);
        cp = fsl_strndup( name, (fsl_int_t)nLen );
        if(!cp){
          rc = FSL_RC_OOM;
          break;
        }
        rc = fsl_list_append(tgt, cp);
        if(rc){
          fsl_free(cp);
          break;
        }
      }
    }
    if(FSL_RC_STEP_DONE==rc) rc = 0;
  }

  end:
  if(rc && !f->error.code && dbR->error.code){
    fsl_cx_uplift_db_error(f, dbR);
  }
  fsl_stmt_finalize(&st);
  fsl_deck_clean(d);
  if(tname.used){
    fsl_db_exec(dbR, "DROP TABLE IF EXISTS %b", &tname);
  }
  fsl_buffer_clear(&tname);
  return rc;
}

int fsl_repo_dir_names( fsl_cx * f, fsl_id_t rid, fsl_list * tgt,
                        bool addSlash ){
  fsl_db * db = (f && tgt) ? fsl_needs_repo(f) : NULL;
  if(!f || !tgt) return FSL_RC_MISUSE;
  else if(!db) return FSL_RC_NOT_A_REPO;
  else {
    int rc;
    if(rid>=0){
      if(!rid){
        /* Dir list for current checkout version */
        if(f->ckout.rid>0){
          rid = f->ckout.rid;
        }else{
          return fsl_cx_err_set(f, FSL_RC_RANGE,
                                "The rid argument is 0 (indicating "
                                "the current checkout), but there is "
                                "no opened checkout.");
        }
      }
      assert(rid>0);
      rc = fsl_repo_dir_names_rid(f, rid, tgt, addSlash);
    }else{
      /* Dir list across all versions */
      fsl_stmt s = fsl_stmt_empty;
      rc = fsl_db_prepare(db, &s,
                          "SELECT DISTINCT(fsl_dirpart(name,%d)) dname "
                          "FROM filename WHERE dname IS NOT NULL "
                          "ORDER BY dname", addSlash ? 1 : 0);
      if(rc){
        fsl_cx_uplift_db_error(f, db);
        assert(!s.stmt);
        return rc;
      }
      while( !rc && (FSL_RC_STEP_ROW==(rc=fsl_stmt_step(&s)))){
        fsl_size_t len = 0;
        char const * col = fsl_stmt_g_text(&s, 0, &len);
        char * cp = fsl_strndup( col, (fsl_int_t)len );
        if(!cp){
          rc = FSL_RC_OOM;
          break;
        }
        rc = fsl_list_append(tgt, cp);
        if(rc) fsl_free(cp);
      }
      if(FSL_RC_STEP_DONE==rc) rc = 0;
      fsl_stmt_finalize(&s);
    }
    return rc;
  }
}

/* UNTESTED */
char fsl_repo_is_readonly(fsl_cx const * f){
  if(!f || !f->dbMain) return 0;
  else{
    int const roleId = f->ckout.db.dbh ? FSL_DBROLE_MAIN : FSL_DBROLE_REPO
      /* If CKOUT is attached, it is the main DB and REPO is ATTACHed. */
      ;
    char const * zRole = fsl_db_role_label(roleId);
    assert(f->dbMain);
    return sqlite3_db_readonly(f->dbMain->dbh, zRole) ? 1 : 0;
  }
}

int fsl_repo_record_filename(fsl_cx * f){
  fsl_buffer full = fsl_buffer_empty;
  fsl_db * dbR = fsl_needs_repo(f);
  fsl_db * dbC;
  fsl_db * dbConf;
  char const * zCDir;
  char const * zName = dbR ? dbR->filename : NULL;
  int rc;
  if(!dbR) return FSL_RC_NOT_A_REPO;
  assert(zName);
  assert(f);
  rc = fsl_file_canonical_name(zName, &full, 0);
  if(rc){
    fsl_cx_err_set(f, rc, "Error %s canonicalizing filename: %s", zName);
    goto end;
  }

  /*
    If global config is open, write the repo db's name to it.
   */
  dbConf = fsl_cx_db_config(f);
  if(dbConf){
    int const dbRole = (f->dbMain==&f->config.db)
      ? FSL_DBROLE_MAIN : FSL_DBROLE_CONFIG;
    rc = fsl_db_exec(dbConf,
                     "INSERT OR IGNORE INTO %s.global_config(name,value) "
                     "VALUES('repo:%q',1)",
                     fsl_db_role_label(dbRole),
                     fsl_buffer_cstr(&full));
    if(rc) goto end;
  }

  dbC = fsl_cx_db_ckout(f);
  if(dbC && (zCDir=f->ckout.dir)){
    /* If we have a checkout, update its repo's list of checkouts... */
    /* Assumption: if we have an opened checkout, dbR is ATTACHed with
       the role REPO. */
    int ro;
    assert(dbR);
    ro = sqlite3_db_readonly(dbR->dbh,
                             fsl_db_role_label(FSL_DBROLE_REPO));
    assert(ro>=0);
    if(!ro){
      fsl_buffer localRoot = fsl_buffer_empty;
      rc = fsl_file_canonical_name(zCDir, &localRoot, 1);
      if(0==rc){
        if(dbConf){
          /*
            If global config is open, write the checkout db's name to it.
          */
          int const dbRole = (f->dbMain==&f->config.db)
            ? FSL_DBROLE_MAIN : FSL_DBROLE_CONFIG;
          rc = fsl_db_exec(dbConf,
                           "REPLACE INTO INTO %s.global_config(name,value) "
                           "VALUES('ckout:%q',1)",
                           fsl_db_role_label(dbRole),
                           fsl_buffer_cstr(&localRoot));
        }
        if(0==rc){
          /* We know that repo is ATTACHed to ckout here. */
          assert(dbR == dbC);
          rc = fsl_db_exec(dbR,
                           "REPLACE INTO %s.config(name, value, mtime) "
                           "VALUES('ckout:%q', 1, now())",
                           fsl_db_role_label(FSL_DBROLE_REPO),
                           fsl_buffer_cstr(&localRoot));
        }
      }
      fsl_buffer_clear(&localRoot);
    }
  }

  end:
  if(rc && !f->error.code && f->dbMain->error.code){
    fsl_cx_uplift_db_error(f, f->dbMain);
  }
  fsl_buffer_clear(&full);
  return rc;

}

char fsl_rid_is_a_checkin(fsl_cx * f, fsl_id_t rid){
  fsl_db * db = f ? fsl_cx_db_repo(f) : NULL;
  if(!db || (rid<0)) return 0;
  else if(0==rid){
    /* Corner case: empty repo */
    return !fsl_db_exists(db, "SELECT 1 FROM blob WHERE rid>0");
  }
  else{
    fsl_stmt * st = 0;
    char rv = 0;
    int rc = fsl_db_prepare_cached(db, &st,
                                   "SELECT 1 FROM event WHERE "
                                   "objid=? AND type='ci' "
                                   "/*%s()*/",__func__);
    if(!rc){
      rc = fsl_stmt_bind_id( st, 1, rid);
      if(!rc){
        rc = fsl_stmt_step(st);
        if(FSL_RC_STEP_ROW==rc){
          rv = 1;
        }
      }
      fsl_stmt_cached_yield(st);
    }
    if(db->error.code){
      fsl_cx_uplift_db_error(f, db);
    }
    return rv;
  }
}

int fsl_repo_extract( fsl_cx * f, fsl_repo_extract_opt const * opt_ ){
  if(!f || !opt_->callback) return FSL_RC_MISUSE;
  else if(!fsl_needs_repo(f)) return FSL_RC_NOT_A_REPO;
  else if(opt_->checkinRid<=0){
    return fsl_cx_err_set(f, FSL_RC_RANGE, "RID must be positive.");
  }else{
    int rc;
    fsl_deck mf = fsl_deck_empty;
    fsl_buffer * content = opt_->extractContent
      ? &f->fileContent
      : NULL;
    fsl_id_t fid;
    fsl_repo_extract_state xst = fsl_repo_extract_state_empty;
    fsl_card_F const * fc = NULL;
    fsl_repo_extract_opt const opt = *opt_
      /* Copy in case the caller modifies it via their callback. If we
         find an interesting use for such modification then we can
         remove this copy. */;
    assert(!content || (!content->used && "Internal misuse of fsl_cx::fileContent"));
    rc = fsl_deck_load_rid(f, &mf, opt.checkinRid, FSL_SATYPE_CHECKIN);
    if(rc) goto end;
    assert(mf.f==f);
    xst.f = f;
    xst.checkinRid = opt.checkinRid;
    xst.callbackState = opt.callbackState;
    xst.content = opt.extractContent ? content : NULL;
    /* Calculate xst.count.fileCount... */
    assert(0==xst.count.fileCount);
    if(mf.B.uuid){/*delta. The only way to count this reliably
                   is to walk though the whole card list. */
      rc = fsl_deck_F_rewind(&mf);
      while( !rc && !(rc=fsl_deck_F_next(&mf, &fc)) && fc){
        ++xst.count.fileCount;
      }
      if(rc) goto end;
      fc = NULL;
    }else{
      xst.count.fileCount = mf.F.used;
    }
    assert(0==xst.count.fileNumber);
    rc = fsl_deck_F_rewind(&mf);
    while( !rc && !(rc=fsl_deck_F_next(&mf, &fc)) && fc){
      assert(fc->uuid
             && "We shouldn't get F-card deletions via fsl_deck_F_next()");
      ++xst.count.fileNumber;
      fid = fsl_uuid_to_rid(f, fc->uuid);
      if(fid<0){
        assert(f->error.code);
        rc = f->error.code;
      }else if(!fid){
        rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                            "Could not resolve RID for UUID: %s",
                            fc->uuid);
      }else if(opt.extractContent){
        fsl_buffer_reuse(content);
        rc = fsl_content_get(f, fid, content);
      }
      if(!rc){
        /** Call the callback. */
        xst.fCard = fc;
        assert(fid>0);
        xst.content = content;
        xst.fileRid = fid;
        rc = opt.callback( &xst );
        if(FSL_RC_BREAK==rc){
          rc = 0;
          break;
        }
      }
    }/* for-each-F-card loop */
    end:
    fsl_cx_content_buffer_yield(f);
    fsl_deck_finalize(&mf);
    return rc;
  }
}

int fsl_repo_import_blob( fsl_cx * f, fsl_input_f in, void * inState,
                          fsl_id_t * rid, fsl_uuid_str * uuid ){
  fsl_db * db = f ? fsl_needs_repo(f) : NULL;
  if(!f || !in) return FSL_RC_MISUSE;
  else if(!db) return FSL_RC_NOT_A_REPO;
  else{
    int rc;
    fsl_buffer buf = fsl_buffer_empty;
    rc = fsl_buffer_fill_from(&buf, in, inState);
    if(rc){
      rc = fsl_cx_err_set(f, rc,
                          "Error filling buffer from input source.");
    }else{
      fsl_id_t theRid = 0;
      rc = fsl_content_put_ex( f, &buf, NULL, 0, 0, 0, &theRid);
      if(!rc){
        if(rid) *rid = theRid;
        if(uuid){
          *uuid = fsl_rid_to_uuid(f, theRid);
          if(!uuid) rc = FSL_RC_OOM;
        }
      }
    }
    fsl_buffer_clear(&buf);
    return rc;
  }
}

int fsl_repo_import_buffer( fsl_cx * f, fsl_buffer const * in,
                            fsl_id_t * rid, fsl_uuid_str * uuid ){
  if(!f || !in) return FSL_RC_MISUSE;
  else{
    /* Workaround: input ptr is const and input needs to modify
       (only) the cursor. So we'll cheat rather than require a non-const
       input...
    */
    fsl_buffer cursorKludge = *in;
    cursorKludge.cursor = 0;
    int const rc = fsl_repo_import_blob(f, fsl_input_f_buffer, &cursorKludge,
                                        rid, uuid );
    assert(cursorKludge.mem == in->mem);
    return rc;
  }
}


int fsl_repo_blob_lookup( fsl_cx * f, fsl_buffer const * src, fsl_id_t * ridOut,
                          fsl_uuid_str * hashOut ){
  int rc;
  fsl_buffer hash_ = fsl_buffer_empty;
  fsl_buffer * hash;
  fsl_id_t rid = 0;
  if(!fsl_cx_db_repo(f)) return FSL_RC_NOT_A_REPO;
  hash = hashOut ? &hash_ : fsl_cx_scratchpad(f);
  /* First check the auxiliary hash to see if there is already an artifact
     that uses the auxiliary hash name */
  rc = fsl_cx_hash_buffer(f, true, src, hash);
  if(FSL_RC_UNSUPPORTED==rc){
    // The auxiliary hash option is incompatible with our hash policy.
    rc = 0;
  }
  else if(rc) goto end;
  rid = hash->used ? fsl_uuid_to_rid(f, fsl_buffer_cstr(hash)) : 0;
  if(!rid){
    /* No existing artifact with the auxiliary hash name.  Therefore, use
       the primary hash name. */
    fsl_buffer_reuse(hash);
    rc = fsl_cx_hash_buffer(f, false, src, hash);
    if(rc) goto end;
    rid = fsl_uuid_to_rid(f, fsl_buffer_cstr(hash));
    if(!rid){
      rc = FSL_RC_NOT_FOUND;
    }
    if(rid<0){
      rc = f->error.code;
    }
  }
  end:
  if(!rc || rc==FSL_RC_NOT_FOUND){
    if(hashOut){
      assert(hash == &hash_);
      *hashOut = fsl_buffer_take(hash)/*transfer*/;
    }
  }
  if(!rc && ridOut){
    *ridOut = rid;
  }
  if(hash == &hash_){
    fsl_buffer_clear(hash);
  }else{
    assert(!hash_.mem);
    fsl_cx_scratchpad_yield(f, hash);
  }
  return rc;
}

int fsl_repo_fingerprint_search(fsl_cx *f, fsl_id_t rcvid, char ** zOut){
  int rc = 0;
  fsl_db * const db = fsl_needs_repo(f);
  if(!db) return FSL_RC_NOT_A_REPO; 
  fsl_buffer * const sql = fsl_cx_scratchpad(f);
  fsl_stmt q = fsl_stmt_empty;
  rc = fsl_buffer_append(sql,
                         "SELECT rcvid, quote(uid), datetime(mtime), "
                         "quote(nonce), quote(ipaddr) "
                         "FROM rcvfrom ", -1);
  if(rc) goto end;
  rc = (rcvid>0)
    ? fsl_buffer_appendf(sql, "WHERE rcvid=%" FSL_ID_T_PFMT, rcvid)
    : fsl_buffer_append(sql, "ORDER BY rcvid DESC LIMIT 1", -1);
  if(rc) goto end;
  rc = fsl_db_prepare(db, &q, "%b", sql);
  if(rc) goto end;
  rc = fsl_stmt_step(&q);
  switch(rc){
    case FSL_RC_STEP_ROW:{
      fsl_md5_cx hash = fsl_md5_cx_empty;
      fsl_size_t len = 0;
      fsl_id_t const rvid = fsl_stmt_g_id(&q, 0);
      unsigned char digest[16] = {0};
      char hex[FSL_STRLEN_MD5+1] = {0};
      for(int i = 1; i <= 4; ++i){
        char const * z = fsl_stmt_g_text(&q, i, &len);
        fsl_md5_update(&hash, z, len);
      }
      fsl_md5_final(&hash, digest);
      fsl_md5_digest_to_base16(digest, hex);
      *zOut = fsl_mprintf("%" FSL_ID_T_PFMT "/%s", rvid, hex);
      rc = *zOut ? 0 : FSL_RC_OOM;
      break;
    }
    case FSL_RC_STEP_DONE:
      rc = FSL_RC_NOT_FOUND;
      break;
    default:
      rc = fsl_cx_uplift_db_error2(f, db, rc);
      break;
  }
  end:
  fsl_cx_scratchpad_yield(f, sql);
  fsl_stmt_finalize(&q);
  return rc;
}


/**
   NOT YET IMPLEMENTED. (We have the infrastructure, just need to glue
   it together.)

   Re-crosslinks all artifacts of the given type (or all artifacts if
   the 2nd argument is FSL_SATYPE_ANY). This is an expensive
   operation, involving dropping the contents of any corresponding
   auxiliary tables, loading and parsing the appropriate artifacts,
   and re-creating the auxiliary tables.

   TODO: add a way for callers to get some sort of progress feedback
   and abort the process by returning non-0 from that handler. We can
   possibly do that via defining an internal-use crosslink listener
   which carries more state, e.g. for calculating completion progress.
*/
//FSL_EXPORT int fsl_repo_relink_artifacts(fsl_cx *f, void * someOptionsType);


#undef MARKER
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/schema.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  Copyright 2013-2021 Stephan Beal (https://wanderinghorse.net).


  
  This program is free software; you can redistribute it and/or
  modify it under the terms of the Simplified BSD License (also
  known as the "2-Clause License" or "FreeBSD License".)
  
  This program is distributed in the hope that it will be useful,
  but without any warranty; without even the implied warranty of
  merchantability or fitness for a particular purpose.
  
  *****************************************************************************
  This file implements schema-related parts of the library.
*/
#include "fossil-scm/fossil-internal.h"
#include <assert.h>


char const * fsl_schema_ckout(){
  extern char const * fsl_schema_ckout_cstr;
  return fsl_schema_ckout_cstr;
}

char const * fsl_schema_repo2(){
  extern char const * fsl_schema_repo2_cstr;
  return fsl_schema_repo2_cstr;
}

char const * fsl_schema_repo1(){
  extern char const * fsl_schema_repo1_cstr;
  return fsl_schema_repo1_cstr;
}

char const * fsl_schema_config(){
  extern char const * fsl_schema_config_cstr;
  return fsl_schema_config_cstr;
}

char const * fsl_schema_ticket_reports(){
  extern char const * fsl_schema_ticket_reports_cstr;
  return fsl_schema_ticket_reports_cstr;
}

char const * fsl_schema_ticket(){
  extern char const * fsl_schema_ticket_cstr;
  return fsl_schema_ticket_cstr;
}

char const * fsl_schema_forum(){
  extern char const * fsl_schema_forum_cstr;
  return fsl_schema_forum_cstr;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
















































































































Deleted src/schema_ckout_cstr.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
/* Binary form of file ../sql/checkout.sql */
/** @page page_schema_ckout_cstr Schema: checkout.sql
@code
-- The VVAR table holds miscellanous information about the local database
-- in the form of name-value pairs.  This is similar to the VAR table
-- table in the repository except that this table holds information that
-- is specific to the local checkout.
--
-- Important Variables:
--
--     repository        Full pathname of the repository database
--     user-id           Userid to use
--
CREATE TABLE ckout.vvar(
  name TEXT PRIMARY KEY NOT NULL,  -- Primary name of the entry
  value CLOB,                      -- Content of the named parameter
  CHECK( typeof(name)='text' AND length(name)>=1 )
);

-- Each entry in the vfile table represents a single file in the
-- current checkout.
--
-- The file.rid field is 0 for files or folders that have been
-- added but not yet committed.
--
-- Vfile.chnged is 0 for unmodified files, 1 for files that have
-- been edited or which have been subjected to a 3-way merge.
-- Vfile.chnged is 2 if the file has been replaced from a different
-- version by the merge and 3 if the file has been added by a merge.
-- Vfile.chnged is 4|5 is the same as 2|3, but the operation has been
-- done by an --integrate merge.  The difference between vfile.chnged==2|4
-- and a regular add is that with vfile.chnged==2|4 we know that the
-- current version of the file is already in the repository.
--
CREATE TABLE ckout.vfile(
  id INTEGER PRIMARY KEY,           -- ID of the checked out file
  vid INTEGER REFERENCES blob,      -- The baseline this file is part of.
  chnged INT DEFAULT 0,             -- 0:unchnged 1:edited 2:m-chng 3:m-add 4:i-chng 5:i-add
  deleted BOOLEAN DEFAULT 0,        -- True if deleted
  isexe BOOLEAN,                    -- True if file should be executable
  islink BOOLEAN,                   -- True if file should be symlink
  rid INTEGER,                      -- Originally from this repository record
  mrid INTEGER,                     -- Based on this record due to a merge
  mtime INTEGER,                    -- Mtime of file on disk. sec since 1970
  pathname TEXT,                    -- Full pathname relative to root
  origname TEXT,                    -- Original pathname. NULL if unchanged
  mhash TEXT,                       -- Hash of mrid iff mrid!=rid. Added 2019-01-19.
  UNIQUE(pathname,vid)
);

-- This table holds a record of uncommitted merges in the local
-- file tree.  If a VFILE entry with id has merged with another
-- record, there is an entry in this table with (id,merge) where
-- merge is the RECORD table entry that the file merged against.
-- An id of 0 or <-3 here means the version record itself.  When
-- id==(-1) that is a cherrypick merge, id==(-2) that is a
-- backout merge and id==(-4) is a integrate merge.

CREATE TABLE ckout.vmerge(
  id INTEGER REFERENCES vfile,      -- VFILE entry that has been merged
  merge INTEGER,                    -- Merged with this record
  mhash TEXT                        -- SHA1/SHA3 hash for merge object
);
CREATE UNIQUE INDEX ckout.vmergex1 ON vmerge(id,mhash);

-- The following trigger will prevent older versions of Fossil that
-- do not know about the new vmerge.mhash column from updating the
-- vmerge table.  This must be done with a trigger, since legacy Fossil
-- uses INSERT OR IGNORE to update vmerge, and the OR IGNORE will cause
-- a NOT NULL constraint to be silently ignored.
CREATE TRIGGER ckout.vmerge_ck1 AFTER INSERT ON vmerge
WHEN new.mhash IS NULL BEGIN
  SELECT raise(FAIL,
  'trying to update a newer checkout with an older version of Fossil');
END;

-- Identifier for this file type.
-- The integer is the same as 'FSLC'.
PRAGMA ckout.application_id=252006674;
 @endcode
 @see schema_ckout()
*/
/* auto-generated code - edit at your own risk! (Good luck with that!) */
static char const fsl_schema_ckout_cstr_a[] = {
45, 45, 32, 84, 104, 101, 32, 86, 86, 65, 82, 32, 116, 97, 98, 108, 101, 32, 104, 111, 
108, 100, 115, 32, 109, 105, 115, 99, 101, 108, 108, 97, 110, 111, 117, 115, 32, 105, 110, 102, 
111, 114, 109, 97, 116, 105, 111, 110, 32, 97, 98, 111, 117, 116, 32, 116, 104, 101, 32, 108, 
111, 99, 97, 108, 32, 100, 97, 116, 97, 98, 97, 115, 101, 10, 45, 45, 32, 105, 110, 32, 
116, 104, 101, 32, 102, 111, 114, 109, 32, 111, 102, 32, 110, 97, 109, 101, 45, 118, 97, 108, 
117, 101, 32, 112, 97, 105, 114, 115, 46, 32, 32, 84, 104, 105, 115, 32, 105, 115, 32, 115, 
105, 109, 105, 108, 97, 114, 32, 116, 111, 32, 116, 104, 101, 32, 86, 65, 82, 32, 116, 97, 
98, 108, 101, 10, 45, 45, 32, 116, 97, 98, 108, 101, 32, 105, 110, 32, 116, 104, 101, 32, 
114, 101, 112, 111, 115, 105, 116, 111, 114, 121, 32, 101, 120, 99, 101, 112, 116, 32, 116, 104, 
97, 116, 32, 116, 104, 105, 115, 32, 116, 97, 98, 108, 101, 32, 104, 111, 108, 100, 115, 32, 
105, 110, 102, 111, 114, 109, 97, 116, 105, 111, 110, 32, 116, 104, 97, 116, 10, 45, 45, 32, 
105, 115, 32, 115, 112, 101, 99, 105, 102, 105, 99, 32, 116, 111, 32, 116, 104, 101, 32, 108, 
111, 99, 97, 108, 32, 99, 104, 101, 99, 107, 111, 117, 116, 46, 10, 45, 45, 10, 45, 45, 
32, 73, 109, 112, 111, 114, 116, 97, 110, 116, 32, 86, 97, 114, 105, 97, 98, 108, 101, 115, 
58, 10, 45, 45, 10, 45, 45, 32, 32, 32, 32, 32, 114, 101, 112, 111, 115, 105, 116, 111, 
114, 121, 32, 32, 32, 32, 32, 32, 32, 32, 70, 117, 108, 108, 32, 112, 97, 116, 104, 110, 
97, 109, 101, 32, 111, 102, 32, 116, 104, 101, 32, 114, 101, 112, 111, 115, 105, 116, 111, 114, 
121, 32, 100, 97, 116, 97, 98, 97, 115, 101, 10, 45, 45, 32, 32, 32, 32, 32, 117, 115, 
101, 114, 45, 105, 100, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 85, 115, 101, 114, 
105, 100, 32, 116, 111, 32, 117, 115, 101, 10, 45, 45, 10, 67, 82, 69, 65, 84, 69, 32, 
84, 65, 66, 76, 69, 32, 99, 107, 111, 117, 116, 46, 118, 118, 97, 114, 40, 10, 32, 32, 
110, 97, 109, 101, 32, 84, 69, 88, 84, 32, 80, 82, 73, 77, 65, 82, 89, 32, 75, 69, 
89, 32, 78, 79, 84, 32, 78, 85, 76, 76, 44, 32, 32, 45, 45, 32, 80, 114, 105, 109, 
97, 114, 121, 32, 110, 97, 109, 101, 32, 111, 102, 32, 116, 104, 101, 32, 101, 110, 116, 114, 
121, 10, 32, 32, 118, 97, 108, 117, 101, 32, 67, 76, 79, 66, 44, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 
67, 111, 110, 116, 101, 110, 116, 32, 111, 102, 32, 116, 104, 101, 32, 110, 97, 109, 101, 100, 
32, 112, 97, 114, 97, 109, 101, 116, 101, 114, 10, 32, 32, 67, 72, 69, 67, 75, 40, 32, 
116, 121, 112, 101, 111, 102, 40, 110, 97, 109, 101, 41, 61, 39, 116, 101, 120, 116, 39, 32, 
65, 78, 68, 32, 108, 101, 110, 103, 116, 104, 40, 110, 97, 109, 101, 41, 62, 61, 49, 32, 
41, 10, 41, 59, 10, 10, 45, 45, 32, 69, 97, 99, 104, 32, 101, 110, 116, 114, 121, 32, 
105, 110, 32, 116, 104, 101, 32, 118, 102, 105, 108, 101, 32, 116, 97, 98, 108, 101, 32, 114, 
101, 112, 114, 101, 115, 101, 110, 116, 115, 32, 97, 32, 115, 105, 110, 103, 108, 101, 32, 102, 
105, 108, 101, 32, 105, 110, 32, 116, 104, 101, 10, 45, 45, 32, 99, 117, 114, 114, 101, 110, 
116, 32, 99, 104, 101, 99, 107, 111, 117, 116, 46, 10, 45, 45, 10, 45, 45, 32, 84, 104, 
101, 32, 102, 105, 108, 101, 46, 114, 105, 100, 32, 102, 105, 101, 108, 100, 32, 105, 115, 32, 
48, 32, 102, 111, 114, 32, 102, 105, 108, 101, 115, 32, 111, 114, 32, 102, 111, 108, 100, 101, 
114, 115, 32, 116, 104, 97, 116, 32, 104, 97, 118, 101, 32, 98, 101, 101, 110, 10, 45, 45, 
32, 97, 100, 100, 101, 100, 32, 98, 117, 116, 32, 110, 111, 116, 32, 121, 101, 116, 32, 99, 
111, 109, 109, 105, 116, 116, 101, 100, 46, 10, 45, 45, 10, 45, 45, 32, 86, 102, 105, 108, 
101, 46, 99, 104, 110, 103, 101, 100, 32, 105, 115, 32, 48, 32, 102, 111, 114, 32, 117, 110, 
109, 111, 100, 105, 102, 105, 101, 100, 32, 102, 105, 108, 101, 115, 44, 32, 49, 32, 102, 111, 
114, 32, 102, 105, 108, 101, 115, 32, 116, 104, 97, 116, 32, 104, 97, 118, 101, 10, 45, 45, 
32, 98, 101, 101, 110, 32, 101, 100, 105, 116, 101, 100, 32, 111, 114, 32, 119, 104, 105, 99, 
104, 32, 104, 97, 118, 101, 32, 98, 101, 101, 110, 32, 115, 117, 98, 106, 101, 99, 116, 101, 
100, 32, 116, 111, 32, 97, 32, 51, 45, 119, 97, 121, 32, 109, 101, 114, 103, 101, 46, 10, 
45, 45, 32, 86, 102, 105, 108, 101, 46, 99, 104, 110, 103, 101, 100, 32, 105, 115, 32, 50, 
32, 105, 102, 32, 116, 104, 101, 32, 102, 105, 108, 101, 32, 104, 97, 115, 32, 98, 101, 101, 
110, 32, 114, 101, 112, 108, 97, 99, 101, 100, 32, 102, 114, 111, 109, 32, 97, 32, 100, 105, 
102, 102, 101, 114, 101, 110, 116, 10, 45, 45, 32, 118, 101, 114, 115, 105, 111, 110, 32, 98, 
121, 32, 116, 104, 101, 32, 109, 101, 114, 103, 101, 32, 97, 110, 100, 32, 51, 32, 105, 102, 
32, 116, 104, 101, 32, 102, 105, 108, 101, 32, 104, 97, 115, 32, 98, 101, 101, 110, 32, 97, 
100, 100, 101, 100, 32, 98, 121, 32, 97, 32, 109, 101, 114, 103, 101, 46, 10, 45, 45, 32, 
86, 102, 105, 108, 101, 46, 99, 104, 110, 103, 101, 100, 32, 105, 115, 32, 52, 124, 53, 32, 
105, 115, 32, 116, 104, 101, 32, 115, 97, 109, 101, 32, 97, 115, 32, 50, 124, 51, 44, 32, 
98, 117, 116, 32, 116, 104, 101, 32, 111, 112, 101, 114, 97, 116, 105, 111, 110, 32, 104, 97, 
115, 32, 98, 101, 101, 110, 10, 45, 45, 32, 100, 111, 110, 101, 32, 98, 121, 32, 97, 110, 
32, 45, 45, 105, 110, 116, 101, 103, 114, 97, 116, 101, 32, 109, 101, 114, 103, 101, 46, 32, 
32, 84, 104, 101, 32, 100, 105, 102, 102, 101, 114, 101, 110, 99, 101, 32, 98, 101, 116, 119, 
101, 101, 110, 32, 118, 102, 105, 108, 101, 46, 99, 104, 110, 103, 101, 100, 61, 61, 50, 124, 
52, 10, 45, 45, 32, 97, 110, 100, 32, 97, 32, 114, 101, 103, 117, 108, 97, 114, 32, 97, 
100, 100, 32, 105, 115, 32, 116, 104, 97, 116, 32, 119, 105, 116, 104, 32, 118, 102, 105, 108, 
101, 46, 99, 104, 110, 103, 101, 100, 61, 61, 50, 124, 52, 32, 119, 101, 32, 107, 110, 111, 
119, 32, 116, 104, 97, 116, 32, 116, 104, 101, 10, 45, 45, 32, 99, 117, 114, 114, 101, 110, 
116, 32, 118, 101, 114, 115, 105, 111, 110, 32, 111, 102, 32, 116, 104, 101, 32, 102, 105, 108, 
101, 32, 105, 115, 32, 97, 108, 114, 101, 97, 100, 121, 32, 105, 110, 32, 116, 104, 101, 32, 
114, 101, 112, 111, 115, 105, 116, 111, 114, 121, 46, 10, 45, 45, 10, 67, 82, 69, 65, 84, 
69, 32, 84, 65, 66, 76, 69, 32, 99, 107, 111, 117, 116, 46, 118, 102, 105, 108, 101, 40, 
10, 32, 32, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 32, 80, 82, 73, 77, 65, 82, 
89, 32, 75, 69, 89, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 
73, 68, 32, 111, 102, 32, 116, 104, 101, 32, 99, 104, 101, 99, 107, 101, 100, 32, 111, 117, 
116, 32, 102, 105, 108, 101, 10, 32, 32, 118, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 
32, 82, 69, 70, 69, 82, 69, 78, 67, 69, 83, 32, 98, 108, 111, 98, 44, 32, 32, 32, 
32, 32, 32, 45, 45, 32, 84, 104, 101, 32, 98, 97, 115, 101, 108, 105, 110, 101, 32, 116, 
104, 105, 115, 32, 102, 105, 108, 101, 32, 105, 115, 32, 112, 97, 114, 116, 32, 111, 102, 46, 
10, 32, 32, 99, 104, 110, 103, 101, 100, 32, 73, 78, 84, 32, 68, 69, 70, 65, 85, 76, 
84, 32, 48, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 
48, 58, 117, 110, 99, 104, 110, 103, 101, 100, 32, 49, 58, 101, 100, 105, 116, 101, 100, 32, 
50, 58, 109, 45, 99, 104, 110, 103, 32, 51, 58, 109, 45, 97, 100, 100, 32, 52, 58, 105, 
45, 99, 104, 110, 103, 32, 53, 58, 105, 45, 97, 100, 100, 10, 32, 32, 100, 101, 108, 101, 
116, 101, 100, 32, 66, 79, 79, 76, 69, 65, 78, 32, 68, 69, 70, 65, 85, 76, 84, 32, 
48, 44, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 84, 114, 117, 101, 32, 105, 102, 
32, 100, 101, 108, 101, 116, 101, 100, 10, 32, 32, 105, 115, 101, 120, 101, 32, 66, 79, 79, 
76, 69, 65, 78, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 45, 45, 32, 84, 114, 117, 101, 32, 105, 102, 32, 102, 105, 108, 101, 
32, 115, 104, 111, 117, 108, 100, 32, 98, 101, 32, 101, 120, 101, 99, 117, 116, 97, 98, 108, 
101, 10, 32, 32, 105, 115, 108, 105, 110, 107, 32, 66, 79, 79, 76, 69, 65, 78, 44, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 
32, 84, 114, 117, 101, 32, 105, 102, 32, 102, 105, 108, 101, 32, 115, 104, 111, 117, 108, 100, 
32, 98, 101, 32, 115, 121, 109, 108, 105, 110, 107, 10, 32, 32, 114, 105, 100, 32, 73, 78, 
84, 69, 71, 69, 82, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 79, 114, 105, 103, 105, 110, 97, 108, 108, 
121, 32, 102, 114, 111, 109, 32, 116, 104, 105, 115, 32, 114, 101, 112, 111, 115, 105, 116, 111, 
114, 121, 32, 114, 101, 99, 111, 114, 100, 10, 32, 32, 109, 114, 105, 100, 32, 73, 78, 84, 
69, 71, 69, 82, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 45, 45, 32, 66, 97, 115, 101, 100, 32, 111, 110, 32, 116, 104, 
105, 115, 32, 114, 101, 99, 111, 114, 100, 32, 100, 117, 101, 32, 116, 111, 32, 97, 32, 109, 
101, 114, 103, 101, 10, 32, 32, 109, 116, 105, 109, 101, 32, 73, 78, 84, 69, 71, 69, 82, 
44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 45, 45, 32, 77, 116, 105, 109, 101, 32, 111, 102, 32, 102, 105, 108, 101, 32, 111, 110, 
32, 100, 105, 115, 107, 46, 32, 115, 101, 99, 32, 115, 105, 110, 99, 101, 32, 49, 57, 55, 
48, 10, 32, 32, 112, 97, 116, 104, 110, 97, 109, 101, 32, 84, 69, 88, 84, 44, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 
32, 70, 117, 108, 108, 32, 112, 97, 116, 104, 110, 97, 109, 101, 32, 114, 101, 108, 97, 116, 
105, 118, 101, 32, 116, 111, 32, 114, 111, 111, 116, 10, 32, 32, 111, 114, 105, 103, 110, 97, 
109, 101, 32, 84, 69, 88, 84, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 79, 114, 105, 103, 105, 110, 97, 108, 32, 
112, 97, 116, 104, 110, 97, 109, 101, 46, 32, 78, 85, 76, 76, 32, 105, 102, 32, 117, 110, 
99, 104, 97, 110, 103, 101, 100, 10, 32, 32, 109, 104, 97, 115, 104, 32, 84, 69, 88, 84, 
44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 45, 45, 32, 72, 97, 115, 104, 32, 111, 102, 32, 109, 114, 105, 100, 32, 
105, 102, 102, 32, 109, 114, 105, 100, 33, 61, 114, 105, 100, 46, 32, 65, 100, 100, 101, 100, 
32, 50, 48, 49, 57, 45, 48, 49, 45, 49, 57, 46, 10, 32, 32, 85, 78, 73, 81, 85, 
69, 40, 112, 97, 116, 104, 110, 97, 109, 101, 44, 118, 105, 100, 41, 10, 41, 59, 10, 10, 
45, 45, 32, 84, 104, 105, 115, 32, 116, 97, 98, 108, 101, 32, 104, 111, 108, 100, 115, 32, 
97, 32, 114, 101, 99, 111, 114, 100, 32, 111, 102, 32, 117, 110, 99, 111, 109, 109, 105, 116, 
116, 101, 100, 32, 109, 101, 114, 103, 101, 115, 32, 105, 110, 32, 116, 104, 101, 32, 108, 111, 
99, 97, 108, 10, 45, 45, 32, 102, 105, 108, 101, 32, 116, 114, 101, 101, 46, 32, 32, 73, 
102, 32, 97, 32, 86, 70, 73, 76, 69, 32, 101, 110, 116, 114, 121, 32, 119, 105, 116, 104, 
32, 105, 100, 32, 104, 97, 115, 32, 109, 101, 114, 103, 101, 100, 32, 119, 105, 116, 104, 32, 
97, 110, 111, 116, 104, 101, 114, 10, 45, 45, 32, 114, 101, 99, 111, 114, 100, 44, 32, 116, 
104, 101, 114, 101, 32, 105, 115, 32, 97, 110, 32, 101, 110, 116, 114, 121, 32, 105, 110, 32, 
116, 104, 105, 115, 32, 116, 97, 98, 108, 101, 32, 119, 105, 116, 104, 32, 40, 105, 100, 44, 
109, 101, 114, 103, 101, 41, 32, 119, 104, 101, 114, 101, 10, 45, 45, 32, 109, 101, 114, 103, 
101, 32, 105, 115, 32, 116, 104, 101, 32, 82, 69, 67, 79, 82, 68, 32, 116, 97, 98, 108, 
101, 32, 101, 110, 116, 114, 121, 32, 116, 104, 97, 116, 32, 116, 104, 101, 32, 102, 105, 108, 
101, 32, 109, 101, 114, 103, 101, 100, 32, 97, 103, 97, 105, 110, 115, 116, 46, 10, 45, 45, 
32, 65, 110, 32, 105, 100, 32, 111, 102, 32, 48, 32, 111, 114, 32, 60, 45, 51, 32, 104, 
101, 114, 101, 32, 109, 101, 97, 110, 115, 32, 116, 104, 101, 32, 118, 101, 114, 115, 105, 111, 
110, 32, 114, 101, 99, 111, 114, 100, 32, 105, 116, 115, 101, 108, 102, 46, 32, 32, 87, 104, 
101, 110, 10, 45, 45, 32, 105, 100, 61, 61, 40, 45, 49, 41, 32, 116, 104, 97, 116, 32, 
105, 115, 32, 97, 32, 99, 104, 101, 114, 114, 121, 112, 105, 99, 107, 32, 109, 101, 114, 103, 
101, 44, 32, 105, 100, 61, 61, 40, 45, 50, 41, 32, 116, 104, 97, 116, 32, 105, 115, 32, 
97, 10, 45, 45, 32, 98, 97, 99, 107, 111, 117, 116, 32, 109, 101, 114, 103, 101, 32, 97, 
110, 100, 32, 105, 100, 61, 61, 40, 45, 52, 41, 32, 105, 115, 32, 97, 32, 105, 110, 116, 
101, 103, 114, 97, 116, 101, 32, 109, 101, 114, 103, 101, 46, 10, 10, 67, 82, 69, 65, 84, 
69, 32, 84, 65, 66, 76, 69, 32, 99, 107, 111, 117, 116, 46, 118, 109, 101, 114, 103, 101, 
40, 10, 32, 32, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 32, 82, 69, 70, 69, 82, 
69, 78, 67, 69, 83, 32, 118, 102, 105, 108, 101, 44, 32, 32, 32, 32, 32, 32, 45, 45, 
32, 86, 70, 73, 76, 69, 32, 101, 110, 116, 114, 121, 32, 116, 104, 97, 116, 32, 104, 97, 
115, 32, 98, 101, 101, 110, 32, 109, 101, 114, 103, 101, 100, 10, 32, 32, 109, 101, 114, 103, 
101, 32, 73, 78, 84, 69, 71, 69, 82, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 77, 101, 114, 103, 101, 100, 32, 
119, 105, 116, 104, 32, 116, 104, 105, 115, 32, 114, 101, 99, 111, 114, 100, 10, 32, 32, 109, 
104, 97, 115, 104, 32, 84, 69, 88, 84, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 83, 72, 65, 49, 
47, 83, 72, 65, 51, 32, 104, 97, 115, 104, 32, 102, 111, 114, 32, 109, 101, 114, 103, 101, 
32, 111, 98, 106, 101, 99, 116, 10, 41, 59, 10, 67, 82, 69, 65, 84, 69, 32, 85, 78, 
73, 81, 85, 69, 32, 73, 78, 68, 69, 88, 32, 99, 107, 111, 117, 116, 46, 118, 109, 101, 
114, 103, 101, 120, 49, 32, 79, 78, 32, 118, 109, 101, 114, 103, 101, 40, 105, 100, 44, 109, 
104, 97, 115, 104, 41, 59, 10, 10, 45, 45, 32, 84, 104, 101, 32, 102, 111, 108, 108, 111, 
119, 105, 110, 103, 32, 116, 114, 105, 103, 103, 101, 114, 32, 119, 105, 108, 108, 32, 112, 114, 
101, 118, 101, 110, 116, 32, 111, 108, 100, 101, 114, 32, 118, 101, 114, 115, 105, 111, 110, 115, 
32, 111, 102, 32, 70, 111, 115, 115, 105, 108, 32, 116, 104, 97, 116, 10, 45, 45, 32, 100, 
111, 32, 110, 111, 116, 32, 107, 110, 111, 119, 32, 97, 98, 111, 117, 116, 32, 116, 104, 101, 
32, 110, 101, 119, 32, 118, 109, 101, 114, 103, 101, 46, 109, 104, 97, 115, 104, 32, 99, 111, 
108, 117, 109, 110, 32, 102, 114, 111, 109, 32, 117, 112, 100, 97, 116, 105, 110, 103, 32, 116, 
104, 101, 10, 45, 45, 32, 118, 109, 101, 114, 103, 101, 32, 116, 97, 98, 108, 101, 46, 32, 
32, 84, 104, 105, 115, 32, 109, 117, 115, 116, 32, 98, 101, 32, 100, 111, 110, 101, 32, 119, 
105, 116, 104, 32, 97, 32, 116, 114, 105, 103, 103, 101, 114, 44, 32, 115, 105, 110, 99, 101, 
32, 108, 101, 103, 97, 99, 121, 32, 70, 111, 115, 115, 105, 108, 10, 45, 45, 32, 117, 115, 
101, 115, 32, 73, 78, 83, 69, 82, 84, 32, 79, 82, 32, 73, 71, 78, 79, 82, 69, 32, 
116, 111, 32, 117, 112, 100, 97, 116, 101, 32, 118, 109, 101, 114, 103, 101, 44, 32, 97, 110, 
100, 32, 116, 104, 101, 32, 79, 82, 32, 73, 71, 78, 79, 82, 69, 32, 119, 105, 108, 108, 
32, 99, 97, 117, 115, 101, 10, 45, 45, 32, 97, 32, 78, 79, 84, 32, 78, 85, 76, 76, 
32, 99, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 116, 111, 32, 98, 101, 32, 115, 105, 
108, 101, 110, 116, 108, 121, 32, 105, 103, 110, 111, 114, 101, 100, 46, 10, 67, 82, 69, 65, 
84, 69, 32, 84, 82, 73, 71, 71, 69, 82, 32, 99, 107, 111, 117, 116, 46, 118, 109, 101, 
114, 103, 101, 95, 99, 107, 49, 32, 65, 70, 84, 69, 82, 32, 73, 78, 83, 69, 82, 84, 
32, 79, 78, 32, 118, 109, 101, 114, 103, 101, 10, 87, 72, 69, 78, 32, 110, 101, 119, 46, 
109, 104, 97, 115, 104, 32, 73, 83, 32, 78, 85, 76, 76, 32, 66, 69, 71, 73, 78, 10, 
32, 32, 83, 69, 76, 69, 67, 84, 32, 114, 97, 105, 115, 101, 40, 70, 65, 73, 76, 44, 
10, 32, 32, 39, 116, 114, 121, 105, 110, 103, 32, 116, 111, 32, 117, 112, 100, 97, 116, 101, 
32, 97, 32, 110, 101, 119, 101, 114, 32, 99, 104, 101, 99, 107, 111, 117, 116, 32, 119, 105, 
116, 104, 32, 97, 110, 32, 111, 108, 100, 101, 114, 32, 118, 101, 114, 115, 105, 111, 110, 32, 
111, 102, 32, 70, 111, 115, 115, 105, 108, 39, 41, 59, 10, 69, 78, 68, 59, 10, 10, 45, 
45, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 102, 111, 114, 32, 116, 104, 105, 
115, 32, 102, 105, 108, 101, 32, 116, 121, 112, 101, 46, 10, 45, 45, 32, 84, 104, 101, 32, 
105, 110, 116, 101, 103, 101, 114, 32, 105, 115, 32, 116, 104, 101, 32, 115, 97, 109, 101, 32, 
97, 115, 32, 39, 70, 83, 76, 67, 39, 46, 10, 80, 82, 65, 71, 77, 65, 32, 99, 107, 
111, 117, 116, 46, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 95, 105, 100, 61, 50, 
53, 50, 48, 48, 54, 54, 55, 52, 59, 10, 
0};
char const * fsl_schema_ckout_cstr = fsl_schema_ckout_cstr_a;
/* end of ../sql/checkout.sql */
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


























































































































































































































































































































































































































































































































































Deleted src/schema_config_cstr.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* Binary form of file ../sql/config.sql */
/** @page page_schema_config_cstr Schema: config.sql
@code
-- This file contains the schema for the database that is kept in the
-- ~/.fossil file and that stores information about the users setup.
--
CREATE TABLE cfg.global_config(
  name TEXT PRIMARY KEY,
  value TEXT
);

-- Identifier for this file type.
-- The integer is the same as 'FSLG'.
PRAGMA cfg.application_id=252006675;
 @endcode
 @see schema_config()
*/
/* auto-generated code - edit at your own risk! (Good luck with that!) */
static char const fsl_schema_config_cstr_a[] = {
45, 45, 32, 84, 104, 105, 115, 32, 102, 105, 108, 101, 32, 99, 111, 110, 116, 97, 105, 110, 
115, 32, 116, 104, 101, 32, 115, 99, 104, 101, 109, 97, 32, 102, 111, 114, 32, 116, 104, 101, 
32, 100, 97, 116, 97, 98, 97, 115, 101, 32, 116, 104, 97, 116, 32, 105, 115, 32, 107, 101, 
112, 116, 32, 105, 110, 32, 116, 104, 101, 10, 45, 45, 32, 126, 47, 46, 102, 111, 115, 115, 
105, 108, 32, 102, 105, 108, 101, 32, 97, 110, 100, 32, 116, 104, 97, 116, 32, 115, 116, 111, 
114, 101, 115, 32, 105, 110, 102, 111, 114, 109, 97, 116, 105, 111, 110, 32, 97, 98, 111, 117, 
116, 32, 116, 104, 101, 32, 117, 115, 101, 114, 115, 32, 115, 101, 116, 117, 112, 46, 10, 45, 
45, 10, 67, 82, 69, 65, 84, 69, 32, 84, 65, 66, 76, 69, 32, 99, 102, 103, 46, 103, 
108, 111, 98, 97, 108, 95, 99, 111, 110, 102, 105, 103, 40, 10, 32, 32, 110, 97, 109, 101, 
32, 84, 69, 88, 84, 32, 80, 82, 73, 77, 65, 82, 89, 32, 75, 69, 89, 44, 10, 32, 
32, 118, 97, 108, 117, 101, 32, 84, 69, 88, 84, 10, 41, 59, 10, 10, 45, 45, 32, 73, 
100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 102, 111, 114, 32, 116, 104, 105, 115, 32, 102, 
105, 108, 101, 32, 116, 121, 112, 101, 46, 10, 45, 45, 32, 84, 104, 101, 32, 105, 110, 116, 
101, 103, 101, 114, 32, 105, 115, 32, 116, 104, 101, 32, 115, 97, 109, 101, 32, 97, 115, 32, 
39, 70, 83, 76, 71, 39, 46, 10, 80, 82, 65, 71, 77, 65, 32, 99, 102, 103, 46, 97, 
112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 95, 105, 100, 61, 50, 53, 50, 48, 48, 54, 
54, 55, 53, 59, 10, 
0};
char const * fsl_schema_config_cstr = fsl_schema_config_cstr_a;
/* end of ../sql/config.sql */
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<














































































Deleted src/schema_forum_cstr.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* Binary form of file ../sql/forum.sql */
/** @page page_schema_forum_cstr Schema: forum.sql
@code
CREATE TABLE repo.forumpost(
  fpid INTEGER PRIMARY KEY,  -- BLOB.rid for the artifact
  froot INT,                 -- fpid of the thread root
  fprev INT,                 -- Previous version of this same post
  firt INT,                  -- This post is in-reply-to
  fmtime REAL                -- When posted.  Julian day
);
CREATE INDEX repo.forumthread ON forumpost(froot,fmtime);
 @endcode
 @see schema_forum()
*/
/* auto-generated code - edit at your own risk! (Good luck with that!) */
static char const fsl_schema_forum_cstr_a[] = {
67, 82, 69, 65, 84, 69, 32, 84, 65, 66, 76, 69, 32, 114, 101, 112, 111, 46, 102, 111, 
114, 117, 109, 112, 111, 115, 116, 40, 10, 32, 32, 102, 112, 105, 100, 32, 73, 78, 84, 69, 
71, 69, 82, 32, 80, 82, 73, 77, 65, 82, 89, 32, 75, 69, 89, 44, 32, 32, 45, 45, 
32, 66, 76, 79, 66, 46, 114, 105, 100, 32, 102, 111, 114, 32, 116, 104, 101, 32, 97, 114, 
116, 105, 102, 97, 99, 116, 10, 32, 32, 102, 114, 111, 111, 116, 32, 73, 78, 84, 44, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 102, 
112, 105, 100, 32, 111, 102, 32, 116, 104, 101, 32, 116, 104, 114, 101, 97, 100, 32, 114, 111, 
111, 116, 10, 32, 32, 102, 112, 114, 101, 118, 32, 73, 78, 84, 44, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 80, 114, 101, 118, 105, 
111, 117, 115, 32, 118, 101, 114, 115, 105, 111, 110, 32, 111, 102, 32, 116, 104, 105, 115, 32, 
115, 97, 109, 101, 32, 112, 111, 115, 116, 10, 32, 32, 102, 105, 114, 116, 32, 73, 78, 84, 
44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 
45, 32, 84, 104, 105, 115, 32, 112, 111, 115, 116, 32, 105, 115, 32, 105, 110, 45, 114, 101, 
112, 108, 121, 45, 116, 111, 10, 32, 32, 102, 109, 116, 105, 109, 101, 32, 82, 69, 65, 76, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 87, 
104, 101, 110, 32, 112, 111, 115, 116, 101, 100, 46, 32, 32, 74, 117, 108, 105, 97, 110, 32, 
100, 97, 121, 10, 41, 59, 10, 67, 82, 69, 65, 84, 69, 32, 73, 78, 68, 69, 88, 32, 
114, 101, 112, 111, 46, 102, 111, 114, 117, 109, 116, 104, 114, 101, 97, 100, 32, 79, 78, 32, 
102, 111, 114, 117, 109, 112, 111, 115, 116, 40, 102, 114, 111, 111, 116, 44, 102, 109, 116, 105, 
109, 101, 41, 59, 10, 
0};
char const * fsl_schema_forum_cstr = fsl_schema_forum_cstr_a;
/* end of ../sql/forum.sql */
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<














































































Deleted src/schema_repo1_cstr.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
/* Binary form of file ../sql/repo-static.sql */
/** @page page_schema_repo1_cstr Schema: repo-static.sql
@code
-- This file contains parts of the schema that are fixed and
-- unchanging across Fossil versions.


-- The BLOB and DELTA tables contain all records held in the repository.
--
-- The BLOB.CONTENT column is always compressed using zlib.  This
-- column might hold the full text of the record or it might hold
-- a delta that is able to reconstruct the record from some other
-- record.  If BLOB.CONTENT holds a delta, then a DELTA table entry
-- will exist for the record and that entry will point to another
-- entry that holds the source of the delta.  Deltas can be chained.
--
-- The blob and delta tables collectively hold the "global state" of
-- a Fossil repository.  
--
CREATE TABLE repo.blob(
  rid INTEGER PRIMARY KEY,        -- Record ID
  rcvid INTEGER,                  -- Origin of this record
  size INTEGER,                   -- Size of content. -1 for a phantom.
  uuid TEXT UNIQUE NOT NULL,      -- SHA1 hash of the content
  content BLOB,                   -- Compressed content of this record
  CHECK( length(uuid)>=40 AND rid>0 )
);
CREATE TABLE repo.delta(
  rid INTEGER PRIMARY KEY,                 -- Record ID
  srcid INTEGER NOT NULL REFERENCES blob   -- Record holding source document
);
CREATE INDEX repo.delta_i1 ON delta(srcid);

-------------------------------------------------------------------------
-- The BLOB and DELTA tables above hold the "global state" of a Fossil
-- project; the stuff that is normally exchanged during "sync".  The
-- "local state" of a repository is contained in the remaining tables of
-- the zRepositorySchema1 string.  
-------------------------------------------------------------------------

-- Whenever new blobs are received into the repository, an entry
-- in this table records the source of the blob.
--
CREATE TABLE repo.rcvfrom(
  rcvid INTEGER PRIMARY KEY,      -- Received-From ID
  uid INTEGER REFERENCES user,    -- User login
  mtime DATETIME,                 -- Time of receipt.  Julian day.
  nonce TEXT UNIQUE,              -- Nonce used for login
  ipaddr TEXT                     -- Remote IP address.  NULL for direct.
);
INSERT INTO repo.rcvfrom(rcvid,uid,mtime,nonce,ipaddr)
VALUES (1, 1, julianday('now'), NULL, NULL);

-- Information about users
--
-- The user.pw field can be either cleartext of the password, or
-- a SHA1 hash of the password.  If the user.pw field is exactly 40
-- characters long we assume it is a SHA1 hash.  Otherwise, it is
-- cleartext.  The sha1_shared_secret() routine computes the password
-- hash based on the project-code, the user login, and the cleartext
-- password.
--
CREATE TABLE repo.user(
  uid INTEGER PRIMARY KEY,        -- User ID
  login TEXT UNIQUE,              -- login name of the user
  pw TEXT,                        -- password
  cap TEXT,                       -- Capabilities of this user
  cookie TEXT,                    -- WWW login cookie
  ipaddr TEXT,                    -- IP address for which cookie is valid
  cexpire DATETIME,               -- Time when cookie expires
  info TEXT,                      -- contact information
  mtime DATE,                     -- last change.  seconds since 1970
  photo BLOB                      -- JPEG image of this user
);

-- The VAR table holds miscellanous information about the repository.
-- in the form of name-value pairs.
--
CREATE TABLE repo.config(
  name TEXT PRIMARY KEY NOT NULL,  -- Primary name of the entry
  value CLOB,                      -- Content of the named parameter
  mtime DATE,                      -- last modified.  seconds since 1970
  CHECK( typeof(name)='text' AND length(name)>=1 )
);

-- Artifacts that should not be processed are identified in the
-- "shun" table.  Artifacts that are control-file forgeries or
-- spam or artifacts whose contents violate administrative policy
-- can be shunned in order to prevent them from contaminating
-- the repository.
--
-- Shunned artifacts do not exist in the blob table.  Hence they
-- have not artifact ID (rid) and we thus must store their full
-- UUID.
--
CREATE TABLE repo.shun(
  uuid UNIQUE,          -- UUID of artifact to be shunned. Canonical form
  mtime DATE,           -- When added.  seconds since 1970
  scom TEXT             -- Optional text explaining why the shun occurred
);

-- Artifacts that should not be pushed are stored in the "private"
-- table.  Private artifacts are omitted from the "unclustered" and
-- "unsent" tables.
--
CREATE TABLE repo.private(rid INTEGER PRIMARY KEY);

-- An entry in this table describes a database query that generates a
-- table of tickets.
--
CREATE TABLE repo.reportfmt(
   rn INTEGER PRIMARY KEY,  -- Report number
   owner TEXT,              -- Owner of this report format (not used)
   title TEXT UNIQUE,       -- Title of this report
   mtime DATE,              -- Last modified.  seconds since 1970
   cols TEXT,               -- A color-key specification
   sqlcode TEXT             -- An SQL SELECT statement for this report
);

-- Some ticket content (such as the originators email address or contact
-- information) needs to be obscured to protect privacy.  This is achieved
-- by storing an SHA1 hash of the content.  For display, the hash is
-- mapped back into the original text using this table.  
--
-- This table contains sensitive information and should not be shared
-- with unauthorized users.
--
CREATE TABLE repo.concealed(
  hash TEXT PRIMARY KEY,    -- The SHA1 hash of content
  mtime DATE,               -- Time created.  Seconds since 1970
  content TEXT              -- Content intended to be concealed
);

-- The application ID helps the unix "file" command to identify the
-- database as a fossil repository.
PRAGMA repo.application_id=252006673;
 @endcode
 @see schema_repo1()
*/
/* auto-generated code - edit at your own risk! (Good luck with that!) */
static char const fsl_schema_repo1_cstr_a[] = {
45, 45, 32, 84, 104, 105, 115, 32, 102, 105, 108, 101, 32, 99, 111, 110, 116, 97, 105, 110, 
115, 32, 112, 97, 114, 116, 115, 32, 111, 102, 32, 116, 104, 101, 32, 115, 99, 104, 101, 109, 
97, 32, 116, 104, 97, 116, 32, 97, 114, 101, 32, 102, 105, 120, 101, 100, 32, 97, 110, 100, 
10, 45, 45, 32, 117, 110, 99, 104, 97, 110, 103, 105, 110, 103, 32, 97, 99, 114, 111, 115, 
115, 32, 70, 111, 115, 115, 105, 108, 32, 118, 101, 114, 115, 105, 111, 110, 115, 46, 10, 10, 
10, 45, 45, 32, 84, 104, 101, 32, 66, 76, 79, 66, 32, 97, 110, 100, 32, 68, 69, 76, 
84, 65, 32, 116, 97, 98, 108, 101, 115, 32, 99, 111, 110, 116, 97, 105, 110, 32, 97, 108, 
108, 32, 114, 101, 99, 111, 114, 100, 115, 32, 104, 101, 108, 100, 32, 105, 110, 32, 116, 104, 
101, 32, 114, 101, 112, 111, 115, 105, 116, 111, 114, 121, 46, 10, 45, 45, 10, 45, 45, 32, 
84, 104, 101, 32, 66, 76, 79, 66, 46, 67, 79, 78, 84, 69, 78, 84, 32, 99, 111, 108, 
117, 109, 110, 32, 105, 115, 32, 97, 108, 119, 97, 121, 115, 32, 99, 111, 109, 112, 114, 101, 
115, 115, 101, 100, 32, 117, 115, 105, 110, 103, 32, 122, 108, 105, 98, 46, 32, 32, 84, 104, 
105, 115, 10, 45, 45, 32, 99, 111, 108, 117, 109, 110, 32, 109, 105, 103, 104, 116, 32, 104, 
111, 108, 100, 32, 116, 104, 101, 32, 102, 117, 108, 108, 32, 116, 101, 120, 116, 32, 111, 102, 
32, 116, 104, 101, 32, 114, 101, 99, 111, 114, 100, 32, 111, 114, 32, 105, 116, 32, 109, 105, 
103, 104, 116, 32, 104, 111, 108, 100, 10, 45, 45, 32, 97, 32, 100, 101, 108, 116, 97, 32, 
116, 104, 97, 116, 32, 105, 115, 32, 97, 98, 108, 101, 32, 116, 111, 32, 114, 101, 99, 111, 
110, 115, 116, 114, 117, 99, 116, 32, 116, 104, 101, 32, 114, 101, 99, 111, 114, 100, 32, 102, 
114, 111, 109, 32, 115, 111, 109, 101, 32, 111, 116, 104, 101, 114, 10, 45, 45, 32, 114, 101, 
99, 111, 114, 100, 46, 32, 32, 73, 102, 32, 66, 76, 79, 66, 46, 67, 79, 78, 84, 69, 
78, 84, 32, 104, 111, 108, 100, 115, 32, 97, 32, 100, 101, 108, 116, 97, 44, 32, 116, 104, 
101, 110, 32, 97, 32, 68, 69, 76, 84, 65, 32, 116, 97, 98, 108, 101, 32, 101, 110, 116, 
114, 121, 10, 45, 45, 32, 119, 105, 108, 108, 32, 101, 120, 105, 115, 116, 32, 102, 111, 114, 
32, 116, 104, 101, 32, 114, 101, 99, 111, 114, 100, 32, 97, 110, 100, 32, 116, 104, 97, 116, 
32, 101, 110, 116, 114, 121, 32, 119, 105, 108, 108, 32, 112, 111, 105, 110, 116, 32, 116, 111, 
32, 97, 110, 111, 116, 104, 101, 114, 10, 45, 45, 32, 101, 110, 116, 114, 121, 32, 116, 104, 
97, 116, 32, 104, 111, 108, 100, 115, 32, 116, 104, 101, 32, 115, 111, 117, 114, 99, 101, 32, 
111, 102, 32, 116, 104, 101, 32, 100, 101, 108, 116, 97, 46, 32, 32, 68, 101, 108, 116, 97, 
115, 32, 99, 97, 110, 32, 98, 101, 32, 99, 104, 97, 105, 110, 101, 100, 46, 10, 45, 45, 
10, 45, 45, 32, 84, 104, 101, 32, 98, 108, 111, 98, 32, 97, 110, 100, 32, 100, 101, 108, 
116, 97, 32, 116, 97, 98, 108, 101, 115, 32, 99, 111, 108, 108, 101, 99, 116, 105, 118, 101, 
108, 121, 32, 104, 111, 108, 100, 32, 116, 104, 101, 32, 34, 103, 108, 111, 98, 97, 108, 32, 
115, 116, 97, 116, 101, 34, 32, 111, 102, 10, 45, 45, 32, 97, 32, 70, 111, 115, 115, 105, 
108, 32, 114, 101, 112, 111, 115, 105, 116, 111, 114, 121, 46, 32, 32, 10, 45, 45, 10, 67, 
82, 69, 65, 84, 69, 32, 84, 65, 66, 76, 69, 32, 114, 101, 112, 111, 46, 98, 108, 111, 
98, 40, 10, 32, 32, 114, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 32, 80, 82, 73, 
77, 65, 82, 89, 32, 75, 69, 89, 44, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 
82, 101, 99, 111, 114, 100, 32, 73, 68, 10, 32, 32, 114, 99, 118, 105, 100, 32, 73, 78, 
84, 69, 71, 69, 82, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 45, 45, 32, 79, 114, 105, 103, 105, 110, 32, 111, 102, 32, 116, 104, 105, 
115, 32, 114, 101, 99, 111, 114, 100, 10, 32, 32, 115, 105, 122, 101, 32, 73, 78, 84, 69, 
71, 69, 82, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 45, 45, 32, 83, 105, 122, 101, 32, 111, 102, 32, 99, 111, 110, 116, 101, 110, 
116, 46, 32, 45, 49, 32, 102, 111, 114, 32, 97, 32, 112, 104, 97, 110, 116, 111, 109, 46, 
10, 32, 32, 117, 117, 105, 100, 32, 84, 69, 88, 84, 32, 85, 78, 73, 81, 85, 69, 32, 
78, 79, 84, 32, 78, 85, 76, 76, 44, 32, 32, 32, 32, 32, 32, 45, 45, 32, 83, 72, 
65, 49, 32, 104, 97, 115, 104, 32, 111, 102, 32, 116, 104, 101, 32, 99, 111, 110, 116, 101, 
110, 116, 10, 32, 32, 99, 111, 110, 116, 101, 110, 116, 32, 66, 76, 79, 66, 44, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 
67, 111, 109, 112, 114, 101, 115, 115, 101, 100, 32, 99, 111, 110, 116, 101, 110, 116, 32, 111, 
102, 32, 116, 104, 105, 115, 32, 114, 101, 99, 111, 114, 100, 10, 32, 32, 67, 72, 69, 67, 
75, 40, 32, 108, 101, 110, 103, 116, 104, 40, 117, 117, 105, 100, 41, 62, 61, 52, 48, 32, 
65, 78, 68, 32, 114, 105, 100, 62, 48, 32, 41, 10, 41, 59, 10, 67, 82, 69, 65, 84, 
69, 32, 84, 65, 66, 76, 69, 32, 114, 101, 112, 111, 46, 100, 101, 108, 116, 97, 40, 10, 
32, 32, 114, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 32, 80, 82, 73, 77, 65, 82, 
89, 32, 75, 69, 89, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 45, 45, 32, 82, 101, 99, 111, 114, 100, 32, 73, 68, 10, 32, 32, 115, 114, 
99, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 32, 78, 79, 84, 32, 78, 85, 76, 76, 
32, 82, 69, 70, 69, 82, 69, 78, 67, 69, 83, 32, 98, 108, 111, 98, 32, 32, 32, 45, 
45, 32, 82, 101, 99, 111, 114, 100, 32, 104, 111, 108, 100, 105, 110, 103, 32, 115, 111, 117, 
114, 99, 101, 32, 100, 111, 99, 117, 109, 101, 110, 116, 10, 41, 59, 10, 67, 82, 69, 65, 
84, 69, 32, 73, 78, 68, 69, 88, 32, 114, 101, 112, 111, 46, 100, 101, 108, 116, 97, 95, 
105, 49, 32, 79, 78, 32, 100, 101, 108, 116, 97, 40, 115, 114, 99, 105, 100, 41, 59, 10, 
10, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 
45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 
45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 
45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 10, 45, 45, 32, 84, 104, 
101, 32, 66, 76, 79, 66, 32, 97, 110, 100, 32, 68, 69, 76, 84, 65, 32, 116, 97, 98, 
108, 101, 115, 32, 97, 98, 111, 118, 101, 32, 104, 111, 108, 100, 32, 116, 104, 101, 32, 34, 
103, 108, 111, 98, 97, 108, 32, 115, 116, 97, 116, 101, 34, 32, 111, 102, 32, 97, 32, 70, 
111, 115, 115, 105, 108, 10, 45, 45, 32, 112, 114, 111, 106, 101, 99, 116, 59, 32, 116, 104, 
101, 32, 115, 116, 117, 102, 102, 32, 116, 104, 97, 116, 32, 105, 115, 32, 110, 111, 114, 109, 
97, 108, 108, 121, 32, 101, 120, 99, 104, 97, 110, 103, 101, 100, 32, 100, 117, 114, 105, 110, 
103, 32, 34, 115, 121, 110, 99, 34, 46, 32, 32, 84, 104, 101, 10, 45, 45, 32, 34, 108, 
111, 99, 97, 108, 32, 115, 116, 97, 116, 101, 34, 32, 111, 102, 32, 97, 32, 114, 101, 112, 
111, 115, 105, 116, 111, 114, 121, 32, 105, 115, 32, 99, 111, 110, 116, 97, 105, 110, 101, 100, 
32, 105, 110, 32, 116, 104, 101, 32, 114, 101, 109, 97, 105, 110, 105, 110, 103, 32, 116, 97, 
98, 108, 101, 115, 32, 111, 102, 10, 45, 45, 32, 116, 104, 101, 32, 122, 82, 101, 112, 111, 
115, 105, 116, 111, 114, 121, 83, 99, 104, 101, 109, 97, 49, 32, 115, 116, 114, 105, 110, 103, 
46, 32, 32, 10, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 
45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 
45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 
45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 10, 10, 45, 
45, 32, 87, 104, 101, 110, 101, 118, 101, 114, 32, 110, 101, 119, 32, 98, 108, 111, 98, 115, 
32, 97, 114, 101, 32, 114, 101, 99, 101, 105, 118, 101, 100, 32, 105, 110, 116, 111, 32, 116, 
104, 101, 32, 114, 101, 112, 111, 115, 105, 116, 111, 114, 121, 44, 32, 97, 110, 32, 101, 110, 
116, 114, 121, 10, 45, 45, 32, 105, 110, 32, 116, 104, 105, 115, 32, 116, 97, 98, 108, 101, 
32, 114, 101, 99, 111, 114, 100, 115, 32, 116, 104, 101, 32, 115, 111, 117, 114, 99, 101, 32, 
111, 102, 32, 116, 104, 101, 32, 98, 108, 111, 98, 46, 10, 45, 45, 10, 67, 82, 69, 65, 
84, 69, 32, 84, 65, 66, 76, 69, 32, 114, 101, 112, 111, 46, 114, 99, 118, 102, 114, 111, 
109, 40, 10, 32, 32, 114, 99, 118, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 32, 80, 
82, 73, 77, 65, 82, 89, 32, 75, 69, 89, 44, 32, 32, 32, 32, 32, 32, 45, 45, 32, 
82, 101, 99, 101, 105, 118, 101, 100, 45, 70, 114, 111, 109, 32, 73, 68, 10, 32, 32, 117, 
105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 32, 82, 69, 70, 69, 82, 69, 78, 67, 69, 
83, 32, 117, 115, 101, 114, 44, 32, 32, 32, 32, 45, 45, 32, 85, 115, 101, 114, 32, 108, 
111, 103, 105, 110, 10, 32, 32, 109, 116, 105, 109, 101, 32, 68, 65, 84, 69, 84, 73, 77, 
69, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 
45, 32, 84, 105, 109, 101, 32, 111, 102, 32, 114, 101, 99, 101, 105, 112, 116, 46, 32, 32, 
74, 117, 108, 105, 97, 110, 32, 100, 97, 121, 46, 10, 32, 32, 110, 111, 110, 99, 101, 32, 
84, 69, 88, 84, 32, 85, 78, 73, 81, 85, 69, 44, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 45, 45, 32, 78, 111, 110, 99, 101, 32, 117, 115, 101, 100, 32, 
102, 111, 114, 32, 108, 111, 103, 105, 110, 10, 32, 32, 105, 112, 97, 100, 100, 114, 32, 84, 
69, 88, 84, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 45, 45, 32, 82, 101, 109, 111, 116, 101, 32, 73, 80, 32, 97, 100, 100, 
114, 101, 115, 115, 46, 32, 32, 78, 85, 76, 76, 32, 102, 111, 114, 32, 100, 105, 114, 101, 
99, 116, 46, 10, 41, 59, 10, 73, 78, 83, 69, 82, 84, 32, 73, 78, 84, 79, 32, 114, 
101, 112, 111, 46, 114, 99, 118, 102, 114, 111, 109, 40, 114, 99, 118, 105, 100, 44, 117, 105, 
100, 44, 109, 116, 105, 109, 101, 44, 110, 111, 110, 99, 101, 44, 105, 112, 97, 100, 100, 114, 
41, 10, 86, 65, 76, 85, 69, 83, 32, 40, 49, 44, 32, 49, 44, 32, 106, 117, 108, 105, 
97, 110, 100, 97, 121, 40, 39, 110, 111, 119, 39, 41, 44, 32, 78, 85, 76, 76, 44, 32, 
78, 85, 76, 76, 41, 59, 10, 10, 45, 45, 32, 73, 110, 102, 111, 114, 109, 97, 116, 105, 
111, 110, 32, 97, 98, 111, 117, 116, 32, 117, 115, 101, 114, 115, 10, 45, 45, 10, 45, 45, 
32, 84, 104, 101, 32, 117, 115, 101, 114, 46, 112, 119, 32, 102, 105, 101, 108, 100, 32, 99, 
97, 110, 32, 98, 101, 32, 101, 105, 116, 104, 101, 114, 32, 99, 108, 101, 97, 114, 116, 101, 
120, 116, 32, 111, 102, 32, 116, 104, 101, 32, 112, 97, 115, 115, 119, 111, 114, 100, 44, 32, 
111, 114, 10, 45, 45, 32, 97, 32, 83, 72, 65, 49, 32, 104, 97, 115, 104, 32, 111, 102, 
32, 116, 104, 101, 32, 112, 97, 115, 115, 119, 111, 114, 100, 46, 32, 32, 73, 102, 32, 116, 
104, 101, 32, 117, 115, 101, 114, 46, 112, 119, 32, 102, 105, 101, 108, 100, 32, 105, 115, 32, 
101, 120, 97, 99, 116, 108, 121, 32, 52, 48, 10, 45, 45, 32, 99, 104, 97, 114, 97, 99, 
116, 101, 114, 115, 32, 108, 111, 110, 103, 32, 119, 101, 32, 97, 115, 115, 117, 109, 101, 32, 
105, 116, 32, 105, 115, 32, 97, 32, 83, 72, 65, 49, 32, 104, 97, 115, 104, 46, 32, 32, 
79, 116, 104, 101, 114, 119, 105, 115, 101, 44, 32, 105, 116, 32, 105, 115, 10, 45, 45, 32, 
99, 108, 101, 97, 114, 116, 101, 120, 116, 46, 32, 32, 84, 104, 101, 32, 115, 104, 97, 49, 
95, 115, 104, 97, 114, 101, 100, 95, 115, 101, 99, 114, 101, 116, 40, 41, 32, 114, 111, 117, 
116, 105, 110, 101, 32, 99, 111, 109, 112, 117, 116, 101, 115, 32, 116, 104, 101, 32, 112, 97, 
115, 115, 119, 111, 114, 100, 10, 45, 45, 32, 104, 97, 115, 104, 32, 98, 97, 115, 101, 100, 
32, 111, 110, 32, 116, 104, 101, 32, 112, 114, 111, 106, 101, 99, 116, 45, 99, 111, 100, 101, 
44, 32, 116, 104, 101, 32, 117, 115, 101, 114, 32, 108, 111, 103, 105, 110, 44, 32, 97, 110, 
100, 32, 116, 104, 101, 32, 99, 108, 101, 97, 114, 116, 101, 120, 116, 10, 45, 45, 32, 112, 
97, 115, 115, 119, 111, 114, 100, 46, 10, 45, 45, 10, 67, 82, 69, 65, 84, 69, 32, 84, 
65, 66, 76, 69, 32, 114, 101, 112, 111, 46, 117, 115, 101, 114, 40, 10, 32, 32, 117, 105, 
100, 32, 73, 78, 84, 69, 71, 69, 82, 32, 80, 82, 73, 77, 65, 82, 89, 32, 75, 69, 
89, 44, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 85, 115, 101, 114, 32, 73, 68, 
10, 32, 32, 108, 111, 103, 105, 110, 32, 84, 69, 88, 84, 32, 85, 78, 73, 81, 85, 69, 
44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 108, 111, 
103, 105, 110, 32, 110, 97, 109, 101, 32, 111, 102, 32, 116, 104, 101, 32, 117, 115, 101, 114, 
10, 32, 32, 112, 119, 32, 84, 69, 88, 84, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 112, 97, 
115, 115, 119, 111, 114, 100, 10, 32, 32, 99, 97, 112, 32, 84, 69, 88, 84, 44, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 45, 45, 32, 67, 97, 112, 97, 98, 105, 108, 105, 116, 105, 101, 115, 32, 111, 102, 32, 
116, 104, 105, 115, 32, 117, 115, 101, 114, 10, 32, 32, 99, 111, 111, 107, 105, 101, 32, 84, 
69, 88, 84, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 45, 45, 32, 87, 87, 87, 32, 108, 111, 103, 105, 110, 32, 99, 111, 111, 
107, 105, 101, 10, 32, 32, 105, 112, 97, 100, 100, 114, 32, 84, 69, 88, 84, 44, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 
32, 73, 80, 32, 97, 100, 100, 114, 101, 115, 115, 32, 102, 111, 114, 32, 119, 104, 105, 99, 
104, 32, 99, 111, 111, 107, 105, 101, 32, 105, 115, 32, 118, 97, 108, 105, 100, 10, 32, 32, 
99, 101, 120, 112, 105, 114, 101, 32, 68, 65, 84, 69, 84, 73, 77, 69, 44, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 84, 105, 109, 101, 32, 
119, 104, 101, 110, 32, 99, 111, 111, 107, 105, 101, 32, 101, 120, 112, 105, 114, 101, 115, 10, 
32, 32, 105, 110, 102, 111, 32, 84, 69, 88, 84, 44, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 99, 111, 110, 
116, 97, 99, 116, 32, 105, 110, 102, 111, 114, 109, 97, 116, 105, 111, 110, 10, 32, 32, 109, 
116, 105, 109, 101, 32, 68, 65, 84, 69, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 108, 97, 115, 116, 32, 99, 
104, 97, 110, 103, 101, 46, 32, 32, 115, 101, 99, 111, 110, 100, 115, 32, 115, 105, 110, 99, 
101, 32, 49, 57, 55, 48, 10, 32, 32, 112, 104, 111, 116, 111, 32, 66, 76, 79, 66, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 45, 45, 32, 74, 80, 69, 71, 32, 105, 109, 97, 103, 101, 32, 111, 102, 32, 116, 104, 
105, 115, 32, 117, 115, 101, 114, 10, 41, 59, 10, 10, 45, 45, 32, 84, 104, 101, 32, 86, 
65, 82, 32, 116, 97, 98, 108, 101, 32, 104, 111, 108, 100, 115, 32, 109, 105, 115, 99, 101, 
108, 108, 97, 110, 111, 117, 115, 32, 105, 110, 102, 111, 114, 109, 97, 116, 105, 111, 110, 32, 
97, 98, 111, 117, 116, 32, 116, 104, 101, 32, 114, 101, 112, 111, 115, 105, 116, 111, 114, 121, 
46, 10, 45, 45, 32, 105, 110, 32, 116, 104, 101, 32, 102, 111, 114, 109, 32, 111, 102, 32, 
110, 97, 109, 101, 45, 118, 97, 108, 117, 101, 32, 112, 97, 105, 114, 115, 46, 10, 45, 45, 
10, 67, 82, 69, 65, 84, 69, 32, 84, 65, 66, 76, 69, 32, 114, 101, 112, 111, 46, 99, 
111, 110, 102, 105, 103, 40, 10, 32, 32, 110, 97, 109, 101, 32, 84, 69, 88, 84, 32, 80, 
82, 73, 77, 65, 82, 89, 32, 75, 69, 89, 32, 78, 79, 84, 32, 78, 85, 76, 76, 44, 
32, 32, 45, 45, 32, 80, 114, 105, 109, 97, 114, 121, 32, 110, 97, 109, 101, 32, 111, 102, 
32, 116, 104, 101, 32, 101, 110, 116, 114, 121, 10, 32, 32, 118, 97, 108, 117, 101, 32, 67, 
76, 79, 66, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 45, 45, 32, 67, 111, 110, 116, 101, 110, 116, 32, 111, 102, 32, 
116, 104, 101, 32, 110, 97, 109, 101, 100, 32, 112, 97, 114, 97, 109, 101, 116, 101, 114, 10, 
32, 32, 109, 116, 105, 109, 101, 32, 68, 65, 84, 69, 44, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 108, 97, 
115, 116, 32, 109, 111, 100, 105, 102, 105, 101, 100, 46, 32, 32, 115, 101, 99, 111, 110, 100, 
115, 32, 115, 105, 110, 99, 101, 32, 49, 57, 55, 48, 10, 32, 32, 67, 72, 69, 67, 75, 
40, 32, 116, 121, 112, 101, 111, 102, 40, 110, 97, 109, 101, 41, 61, 39, 116, 101, 120, 116, 
39, 32, 65, 78, 68, 32, 108, 101, 110, 103, 116, 104, 40, 110, 97, 109, 101, 41, 62, 61, 
49, 32, 41, 10, 41, 59, 10, 10, 45, 45, 32, 65, 114, 116, 105, 102, 97, 99, 116, 115, 
32, 116, 104, 97, 116, 32, 115, 104, 111, 117, 108, 100, 32, 110, 111, 116, 32, 98, 101, 32, 
112, 114, 111, 99, 101, 115, 115, 101, 100, 32, 97, 114, 101, 32, 105, 100, 101, 110, 116, 105, 
102, 105, 101, 100, 32, 105, 110, 32, 116, 104, 101, 10, 45, 45, 32, 34, 115, 104, 117, 110, 
34, 32, 116, 97, 98, 108, 101, 46, 32, 32, 65, 114, 116, 105, 102, 97, 99, 116, 115, 32, 
116, 104, 97, 116, 32, 97, 114, 101, 32, 99, 111, 110, 116, 114, 111, 108, 45, 102, 105, 108, 
101, 32, 102, 111, 114, 103, 101, 114, 105, 101, 115, 32, 111, 114, 10, 45, 45, 32, 115, 112, 
97, 109, 32, 111, 114, 32, 97, 114, 116, 105, 102, 97, 99, 116, 115, 32, 119, 104, 111, 115, 
101, 32, 99, 111, 110, 116, 101, 110, 116, 115, 32, 118, 105, 111, 108, 97, 116, 101, 32, 97, 
100, 109, 105, 110, 105, 115, 116, 114, 97, 116, 105, 118, 101, 32, 112, 111, 108, 105, 99, 121, 
10, 45, 45, 32, 99, 97, 110, 32, 98, 101, 32, 115, 104, 117, 110, 110, 101, 100, 32, 105, 
110, 32, 111, 114, 100, 101, 114, 32, 116, 111, 32, 112, 114, 101, 118, 101, 110, 116, 32, 116, 
104, 101, 109, 32, 102, 114, 111, 109, 32, 99, 111, 110, 116, 97, 109, 105, 110, 97, 116, 105, 
110, 103, 10, 45, 45, 32, 116, 104, 101, 32, 114, 101, 112, 111, 115, 105, 116, 111, 114, 121, 
46, 10, 45, 45, 10, 45, 45, 32, 83, 104, 117, 110, 110, 101, 100, 32, 97, 114, 116, 105, 
102, 97, 99, 116, 115, 32, 100, 111, 32, 110, 111, 116, 32, 101, 120, 105, 115, 116, 32, 105, 
110, 32, 116, 104, 101, 32, 98, 108, 111, 98, 32, 116, 97, 98, 108, 101, 46, 32, 32, 72, 
101, 110, 99, 101, 32, 116, 104, 101, 121, 10, 45, 45, 32, 104, 97, 118, 101, 32, 110, 111, 
116, 32, 97, 114, 116, 105, 102, 97, 99, 116, 32, 73, 68, 32, 40, 114, 105, 100, 41, 32, 
97, 110, 100, 32, 119, 101, 32, 116, 104, 117, 115, 32, 109, 117, 115, 116, 32, 115, 116, 111, 
114, 101, 32, 116, 104, 101, 105, 114, 32, 102, 117, 108, 108, 10, 45, 45, 32, 85, 85, 73, 
68, 46, 10, 45, 45, 10, 67, 82, 69, 65, 84, 69, 32, 84, 65, 66, 76, 69, 32, 114, 
101, 112, 111, 46, 115, 104, 117, 110, 40, 10, 32, 32, 117, 117, 105, 100, 32, 85, 78, 73, 
81, 85, 69, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 85, 85, 73, 
68, 32, 111, 102, 32, 97, 114, 116, 105, 102, 97, 99, 116, 32, 116, 111, 32, 98, 101, 32, 
115, 104, 117, 110, 110, 101, 100, 46, 32, 67, 97, 110, 111, 110, 105, 99, 97, 108, 32, 102, 
111, 114, 109, 10, 32, 32, 109, 116, 105, 109, 101, 32, 68, 65, 84, 69, 44, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 87, 104, 101, 110, 32, 97, 100, 100, 101, 
100, 46, 32, 32, 115, 101, 99, 111, 110, 100, 115, 32, 115, 105, 110, 99, 101, 32, 49, 57, 
55, 48, 10, 32, 32, 115, 99, 111, 109, 32, 84, 69, 88, 84, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 79, 112, 116, 105, 111, 110, 97, 108, 32, 116, 
101, 120, 116, 32, 101, 120, 112, 108, 97, 105, 110, 105, 110, 103, 32, 119, 104, 121, 32, 116, 
104, 101, 32, 115, 104, 117, 110, 32, 111, 99, 99, 117, 114, 114, 101, 100, 10, 41, 59, 10, 
10, 45, 45, 32, 65, 114, 116, 105, 102, 97, 99, 116, 115, 32, 116, 104, 97, 116, 32, 115, 
104, 111, 117, 108, 100, 32, 110, 111, 116, 32, 98, 101, 32, 112, 117, 115, 104, 101, 100, 32, 
97, 114, 101, 32, 115, 116, 111, 114, 101, 100, 32, 105, 110, 32, 116, 104, 101, 32, 34, 112, 
114, 105, 118, 97, 116, 101, 34, 10, 45, 45, 32, 116, 97, 98, 108, 101, 46, 32, 32, 80, 
114, 105, 118, 97, 116, 101, 32, 97, 114, 116, 105, 102, 97, 99, 116, 115, 32, 97, 114, 101, 
32, 111, 109, 105, 116, 116, 101, 100, 32, 102, 114, 111, 109, 32, 116, 104, 101, 32, 34, 117, 
110, 99, 108, 117, 115, 116, 101, 114, 101, 100, 34, 32, 97, 110, 100, 10, 45, 45, 32, 34, 
117, 110, 115, 101, 110, 116, 34, 32, 116, 97, 98, 108, 101, 115, 46, 10, 45, 45, 10, 67, 
82, 69, 65, 84, 69, 32, 84, 65, 66, 76, 69, 32, 114, 101, 112, 111, 46, 112, 114, 105, 
118, 97, 116, 101, 40, 114, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 32, 80, 82, 73, 
77, 65, 82, 89, 32, 75, 69, 89, 41, 59, 10, 10, 45, 45, 32, 65, 110, 32, 101, 110, 
116, 114, 121, 32, 105, 110, 32, 116, 104, 105, 115, 32, 116, 97, 98, 108, 101, 32, 100, 101, 
115, 99, 114, 105, 98, 101, 115, 32, 97, 32, 100, 97, 116, 97, 98, 97, 115, 101, 32, 113, 
117, 101, 114, 121, 32, 116, 104, 97, 116, 32, 103, 101, 110, 101, 114, 97, 116, 101, 115, 32, 
97, 10, 45, 45, 32, 116, 97, 98, 108, 101, 32, 111, 102, 32, 116, 105, 99, 107, 101, 116, 
115, 46, 10, 45, 45, 10, 67, 82, 69, 65, 84, 69, 32, 84, 65, 66, 76, 69, 32, 114, 
101, 112, 111, 46, 114, 101, 112, 111, 114, 116, 102, 109, 116, 40, 10, 32, 32, 32, 114, 110, 
32, 73, 78, 84, 69, 71, 69, 82, 32, 80, 82, 73, 77, 65, 82, 89, 32, 75, 69, 89, 
44, 32, 32, 45, 45, 32, 82, 101, 112, 111, 114, 116, 32, 110, 117, 109, 98, 101, 114, 10, 
32, 32, 32, 111, 119, 110, 101, 114, 32, 84, 69, 88, 84, 44, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 79, 119, 110, 101, 114, 32, 111, 102, 32, 
116, 104, 105, 115, 32, 114, 101, 112, 111, 114, 116, 32, 102, 111, 114, 109, 97, 116, 32, 40, 
110, 111, 116, 32, 117, 115, 101, 100, 41, 10, 32, 32, 32, 116, 105, 116, 108, 101, 32, 84, 
69, 88, 84, 32, 85, 78, 73, 81, 85, 69, 44, 32, 32, 32, 32, 32, 32, 32, 45, 45, 
32, 84, 105, 116, 108, 101, 32, 111, 102, 32, 116, 104, 105, 115, 32, 114, 101, 112, 111, 114, 
116, 10, 32, 32, 32, 109, 116, 105, 109, 101, 32, 68, 65, 84, 69, 44, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 76, 97, 115, 116, 32, 109, 111, 
100, 105, 102, 105, 101, 100, 46, 32, 32, 115, 101, 99, 111, 110, 100, 115, 32, 115, 105, 110, 
99, 101, 32, 49, 57, 55, 48, 10, 32, 32, 32, 99, 111, 108, 115, 32, 84, 69, 88, 84, 
44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 65, 
32, 99, 111, 108, 111, 114, 45, 107, 101, 121, 32, 115, 112, 101, 99, 105, 102, 105, 99, 97, 
116, 105, 111, 110, 10, 32, 32, 32, 115, 113, 108, 99, 111, 100, 101, 32, 84, 69, 88, 84, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 65, 110, 32, 83, 
81, 76, 32, 83, 69, 76, 69, 67, 84, 32, 115, 116, 97, 116, 101, 109, 101, 110, 116, 32, 
102, 111, 114, 32, 116, 104, 105, 115, 32, 114, 101, 112, 111, 114, 116, 10, 41, 59, 10, 10, 
45, 45, 32, 83, 111, 109, 101, 32, 116, 105, 99, 107, 101, 116, 32, 99, 111, 110, 116, 101, 
110, 116, 32, 40, 115, 117, 99, 104, 32, 97, 115, 32, 116, 104, 101, 32, 111, 114, 105, 103, 
105, 110, 97, 116, 111, 114, 115, 32, 101, 109, 97, 105, 108, 32, 97, 100, 100, 114, 101, 115, 
115, 32, 111, 114, 32, 99, 111, 110, 116, 97, 99, 116, 10, 45, 45, 32, 105, 110, 102, 111, 
114, 109, 97, 116, 105, 111, 110, 41, 32, 110, 101, 101, 100, 115, 32, 116, 111, 32, 98, 101, 
32, 111, 98, 115, 99, 117, 114, 101, 100, 32, 116, 111, 32, 112, 114, 111, 116, 101, 99, 116, 
32, 112, 114, 105, 118, 97, 99, 121, 46, 32, 32, 84, 104, 105, 115, 32, 105, 115, 32, 97, 
99, 104, 105, 101, 118, 101, 100, 10, 45, 45, 32, 98, 121, 32, 115, 116, 111, 114, 105, 110, 
103, 32, 97, 110, 32, 83, 72, 65, 49, 32, 104, 97, 115, 104, 32, 111, 102, 32, 116, 104, 
101, 32, 99, 111, 110, 116, 101, 110, 116, 46, 32, 32, 70, 111, 114, 32, 100, 105, 115, 112, 
108, 97, 121, 44, 32, 116, 104, 101, 32, 104, 97, 115, 104, 32, 105, 115, 10, 45, 45, 32, 
109, 97, 112, 112, 101, 100, 32, 98, 97, 99, 107, 32, 105, 110, 116, 111, 32, 116, 104, 101, 
32, 111, 114, 105, 103, 105, 110, 97, 108, 32, 116, 101, 120, 116, 32, 117, 115, 105, 110, 103, 
32, 116, 104, 105, 115, 32, 116, 97, 98, 108, 101, 46, 32, 32, 10, 45, 45, 10, 45, 45, 
32, 84, 104, 105, 115, 32, 116, 97, 98, 108, 101, 32, 99, 111, 110, 116, 97, 105, 110, 115, 
32, 115, 101, 110, 115, 105, 116, 105, 118, 101, 32, 105, 110, 102, 111, 114, 109, 97, 116, 105, 
111, 110, 32, 97, 110, 100, 32, 115, 104, 111, 117, 108, 100, 32, 110, 111, 116, 32, 98, 101, 
32, 115, 104, 97, 114, 101, 100, 10, 45, 45, 32, 119, 105, 116, 104, 32, 117, 110, 97, 117, 
116, 104, 111, 114, 105, 122, 101, 100, 32, 117, 115, 101, 114, 115, 46, 10, 45, 45, 10, 67, 
82, 69, 65, 84, 69, 32, 84, 65, 66, 76, 69, 32, 114, 101, 112, 111, 46, 99, 111, 110, 
99, 101, 97, 108, 101, 100, 40, 10, 32, 32, 104, 97, 115, 104, 32, 84, 69, 88, 84, 32, 
80, 82, 73, 77, 65, 82, 89, 32, 75, 69, 89, 44, 32, 32, 32, 32, 45, 45, 32, 84, 
104, 101, 32, 83, 72, 65, 49, 32, 104, 97, 115, 104, 32, 111, 102, 32, 99, 111, 110, 116, 
101, 110, 116, 10, 32, 32, 109, 116, 105, 109, 101, 32, 68, 65, 84, 69, 44, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 84, 105, 109, 101, 32, 
99, 114, 101, 97, 116, 101, 100, 46, 32, 32, 83, 101, 99, 111, 110, 100, 115, 32, 115, 105, 
110, 99, 101, 32, 49, 57, 55, 48, 10, 32, 32, 99, 111, 110, 116, 101, 110, 116, 32, 84, 
69, 88, 84, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 
67, 111, 110, 116, 101, 110, 116, 32, 105, 110, 116, 101, 110, 100, 101, 100, 32, 116, 111, 32, 
98, 101, 32, 99, 111, 110, 99, 101, 97, 108, 101, 100, 10, 41, 59, 10, 10, 45, 45, 32, 
84, 104, 101, 32, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 32, 73, 68, 32, 104, 
101, 108, 112, 115, 32, 116, 104, 101, 32, 117, 110, 105, 120, 32, 34, 102, 105, 108, 101, 34, 
32, 99, 111, 109, 109, 97, 110, 100, 32, 116, 111, 32, 105, 100, 101, 110, 116, 105, 102, 121, 
32, 116, 104, 101, 10, 45, 45, 32, 100, 97, 116, 97, 98, 97, 115, 101, 32, 97, 115, 32, 
97, 32, 102, 111, 115, 115, 105, 108, 32, 114, 101, 112, 111, 115, 105, 116, 111, 114, 121, 46, 
10, 80, 82, 65, 71, 77, 65, 32, 114, 101, 112, 111, 46, 97, 112, 112, 108, 105, 99, 97, 
116, 105, 111, 110, 95, 105, 100, 61, 50, 53, 50, 48, 48, 54, 54, 55, 51, 59, 10, 
0};
char const * fsl_schema_repo1_cstr = fsl_schema_repo1_cstr_a;
/* end of ../sql/repo-static.sql */
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


























































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/schema_repo2_cstr.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
/* Binary form of file ../sql/repo-transient.sql */
/** @page page_schema_repo2_cstr Schema: repo-transient.sql
@code
-- This file contains parts of the schema that can change from one
-- version to the next. The data stored in these tables is
-- reconstructed from the information in the main repo schema by the
-- "rebuild" operation.

-- Filenames
--
CREATE TABLE repo.filename(
  fnid INTEGER PRIMARY KEY,    -- Filename ID
  name TEXT UNIQUE             -- Name of file page
);

-- Linkages between check-ins, files created by each check-in, and
-- the names of those files.
--
-- Each entry represents a file that changed content from pid to fid
-- due to the check-in that goes from pmid to mid.  fnid is the name
-- of the file in the mid check-in.  If the file was renamed as part
-- of the mid check-in, then pfnid is the previous filename.
--
-- There can be multiple entries for (mid,fid) if the mid check-in was
-- a merge.  Entries with isaux==0 are from the primary parent.  Merge
-- parents have isaux set to true.
--
-- Field name mnemonics:
--    mid = Manifest ID.  (Each check-in is stored as a "Manifest")
--    fid = File ID.
--    pmid = Parent Manifest ID.
--    pid = Parent file ID.
--    fnid = File Name ID.
--    pfnid = Parent File Name ID.
--    isaux = pmid IS AUXiliary parent, not primary parent
--
-- pid==0    if the file is added by check-in mid.
-- pid==(-1) if the file exists in a merge parents but not in the primary
--           parent.  In other words, if the file file was added by merge.
--           (TODO: confirm if/where this is used in fossil and then make sure
--           libfossil does so, too.)
-- fid==0    if the file is removed by check-in mid.
--
CREATE TABLE repo.mlink(
  mid INTEGER,        -- Check-in that contains fid
  fid INTEGER,        -- New file content RID. 0 if deleted
  pmid INTEGER,       -- Check-in RID that contains pid
  pid INTEGER,        -- Prev file content RID. 0 if new. -1 if from a merge
  fnid INTEGER REFERENCES filename,   -- Name of the file
  pfnid INTEGER,      -- Previous name. 0 if unchanged
  mperm INTEGER,                      -- File permissions.  1==exec
  isaux BOOLEAN DEFAULT 0             -- TRUE if pmid is the primary
);
CREATE INDEX repo.mlink_i1 ON mlink(mid);
CREATE INDEX repo.mlink_i2 ON mlink(fnid);
CREATE INDEX repo.mlink_i3 ON mlink(fid);
CREATE INDEX repo.mlink_i4 ON mlink(pid);

-- Parent/child linkages between checkins
--
CREATE TABLE repo.plink(
  pid INTEGER REFERENCES blob,    -- Parent manifest
  cid INTEGER REFERENCES blob,    -- Child manifest
  isprim BOOLEAN,                 -- pid is the primary parent of cid
  mtime DATETIME,                 -- the date/time stamp on cid.  Julian day.
  baseid INTEGER REFERENCES blob, -- Baseline if cid is a delta manifest.
  UNIQUE(pid, cid)
);
CREATE INDEX repo.plink_i2 ON plink(cid,pid);

-- A "leaf" checkin is a checkin that has no children in the same
-- branch.  The set of all leaves is easily computed with a join,
-- between the plink and tagxref tables, but it is a slower join for
-- very large repositories (repositories with 100,000 or more checkins)
-- and so it makes sense to precompute the set of leaves.  There is
-- one entry in the following table for each leaf.
--
CREATE TABLE repo.leaf(rid INTEGER PRIMARY KEY);

-- Events used to generate a timeline
--
CREATE TABLE repo.event(
  type TEXT,                      -- Type of event: 'ci', 'w', 'e', 't', 'g'
  mtime DATETIME,                 -- Time of occurrence. Julian day.
  objid INTEGER PRIMARY KEY,      -- Associated record ID
  tagid INTEGER,                  -- Associated ticket or wiki name tag
  uid INTEGER REFERENCES user,    -- User who caused the event
  bgcolor TEXT,                   -- Color set by 'bgcolor' property
  euser TEXT,                     -- User set by 'user' property
  user TEXT,                      -- Name of the user
  ecomment TEXT,                  -- Comment set by 'comment' property
  comment TEXT,                   -- Comment describing the event
  brief TEXT,                     -- Short comment when tagid already seen
  omtime DATETIME                 -- Original unchanged date+time, or NULL
);
CREATE INDEX repo.event_i1 ON event(mtime);

-- A record of phantoms.  A phantom is a record for which we know the
-- UUID but we do not (yet) know the file content.
--
CREATE TABLE repo.phantom(
  rid INTEGER PRIMARY KEY         -- Record ID of the phantom
);

-- A record of orphaned delta-manifests.  An orphan is a delta-manifest
-- for which we have content, but its baseline-manifest is a phantom.
-- We have to track all orphan manifests so that when the baseline arrives,
-- we know to process the orphaned deltas.
CREATE TABLE repo.orphan(
  rid INTEGER PRIMARY KEY,        -- Delta manifest with a phantom baseline
  baseline INTEGER                -- Phantom baseline of this orphan
);
CREATE INDEX repo.orphan_baseline ON orphan(baseline);

-- Unclustered records.  An unclustered record is a record (including
-- a cluster records themselves) that is not mentioned by some other
-- cluster.
--
-- Phantoms are usually included in the unclustered table.  A new cluster
-- will never be created that contains a phantom.  But another repository
-- might send us a cluster that contains entries that are phantoms to
-- us.
--
CREATE TABLE repo.unclustered(
  rid INTEGER PRIMARY KEY         -- Record ID of the unclustered file
);

-- Records which have never been pushed to another server.  This is
-- used to reduce push operations to a single HTTP request in the
-- common case when one repository only talks to a single server.
--
CREATE TABLE repo.unsent(
  rid INTEGER PRIMARY KEY         -- Record ID of the phantom
);

-- Each baseline or manifest can have one or more tags.  A tag
-- is defined by a row in the next table.
-- 
-- Wiki pages are tagged with "wiki-NAME" where NAME is the name of
-- the wiki page.  Tickets changes are tagged with "ticket-UUID" where 
-- UUID is the indentifier of the ticket.  Tags used to assign symbolic
-- names to baselines are branches are of the form "sym-NAME" where
-- NAME is the symbolic name.
--
CREATE TABLE repo.tag(
  tagid INTEGER PRIMARY KEY,       -- Numeric tag ID
  tagname TEXT UNIQUE              -- Tag name.
);
INSERT INTO repo.tag VALUES(1, 'bgcolor');         -- FSL_TAGID_BGCOLOR
INSERT INTO repo.tag VALUES(2, 'comment');         -- FSL_TAGID_COMMENT
INSERT INTO repo.tag VALUES(3, 'user');            -- FSL_TAGID_USER
INSERT INTO repo.tag VALUES(4, 'date');            -- FSL_TAGID_DATE
INSERT INTO repo.tag VALUES(5, 'hidden');          -- FSL_TAGID_HIDDEN
INSERT INTO repo.tag VALUES(6, 'private');         -- FSL_TAGID_PRIVATE
INSERT INTO repo.tag VALUES(7, 'cluster');         -- FSL_TAGID_CLUSTER
INSERT INTO repo.tag VALUES(8, 'branch');          -- FSL_TAGID_BRANCH
INSERT INTO repo.tag VALUES(9, 'closed');          -- FSL_TAGID_CLOSED
INSERT INTO repo.tag VALUES(10,'parent');          -- FSL_TAGID_PARENT
INSERT INTO repo.tag VALUES(11,'note');            -- FSL_TAG_NOTE
-- arguable, to force auto-increment to start at 100:
-- INSERT INTO tag VALUES(99,'FSL_TAGID_MAX_INTERNAL');

-- Assignments of tags to baselines.  Note that we allow tags to
-- have values assigned to them.  So we are not really dealing with
-- tags here.  These are really properties.  But we are going to
-- keep calling them tags because in many cases the value is ignored.
--
CREATE TABLE repo.tagxref(
  tagid INTEGER REFERENCES tag,   -- The tag that was added or removed
  tagtype INTEGER,                -- 0:-,cancel  1:+,single  2:*,propagate
  srcid INTEGER REFERENCES blob,  -- Artifact of tag. 0 for propagated tags
  origid INTEGER REFERENCES blob, -- check-in holding propagated tag
  value TEXT,                     -- Value of the tag.  Might be NULL.
  mtime TIMESTAMP,                -- Time of addition or removal. Julian day
  rid INTEGER REFERENCE blob,     -- Artifact tag is applied to
  UNIQUE(rid, tagid)
);
CREATE INDEX repo.tagxref_i1 ON tagxref(tagid, mtime);

-- When a hyperlink occurs from one artifact to another (for example
-- when a check-in comment refers to a ticket) an entry is made in
-- the following table for that hyperlink.  This table is used to
-- facilitate the display of "back links".
--
CREATE TABLE repo.backlink(
  target TEXT,           -- Where the hyperlink points to
  srctype INT,           -- 0: check-in  1: ticket  2: wiki
  srcid INT,             -- rid for checkin or wiki.  tkt_id for ticket.
  mtime TIMESTAMP,       -- time that the hyperlink was added. Julian day.
  UNIQUE(target, srctype, srcid)
);
CREATE INDEX repo.backlink_src ON backlink(srcid, srctype);

-- Each attachment is an entry in the following table.  Only
-- the most recent attachment (identified by the D card) is saved.
--
CREATE TABLE repo.attachment(
  attachid INTEGER PRIMARY KEY,   -- Local id for this attachment
  isLatest BOOLEAN DEFAULT 0,     -- True if this is the one to use
  mtime TIMESTAMP,                -- Last changed.  Julian day.
  src TEXT,                       -- UUID of the attachment.  NULL to delete
  target TEXT,                    -- Object attached to. Wikiname or Tkt UUID
  filename TEXT,                  -- Filename for the attachment
  comment TEXT,                   -- Comment associated with this attachment
  user TEXT                       -- Name of user adding attachment
);
CREATE INDEX repo.attachment_idx1 ON attachment(target, filename, mtime);
CREATE INDEX repo.attachment_idx2 ON attachment(src);

-- For tracking cherrypick merges
CREATE TABLE repo.cherrypick(
  parentid INT,
  childid INT,
  isExclude BOOLEAN DEFAULT false,
  PRIMARY KEY(parentid, childid)
) WITHOUT ROWID;
CREATE INDEX repo.cherrypick_cid ON cherrypick(childid);
 @endcode
 @see schema_repo2()
*/
/* auto-generated code - edit at your own risk! (Good luck with that!) */
static char const fsl_schema_repo2_cstr_a[] = {
45, 45, 32, 84, 104, 105, 115, 32, 102, 105, 108, 101, 32, 99, 111, 110, 116, 97, 105, 110, 
115, 32, 112, 97, 114, 116, 115, 32, 111, 102, 32, 116, 104, 101, 32, 115, 99, 104, 101, 109, 
97, 32, 116, 104, 97, 116, 32, 99, 97, 110, 32, 99, 104, 97, 110, 103, 101, 32, 102, 114, 
111, 109, 32, 111, 110, 101, 10, 45, 45, 32, 118, 101, 114, 115, 105, 111, 110, 32, 116, 111, 
32, 116, 104, 101, 32, 110, 101, 120, 116, 46, 32, 84, 104, 101, 32, 100, 97, 116, 97, 32, 
115, 116, 111, 114, 101, 100, 32, 105, 110, 32, 116, 104, 101, 115, 101, 32, 116, 97, 98, 108, 
101, 115, 32, 105, 115, 10, 45, 45, 32, 114, 101, 99, 111, 110, 115, 116, 114, 117, 99, 116, 
101, 100, 32, 102, 114, 111, 109, 32, 116, 104, 101, 32, 105, 110, 102, 111, 114, 109, 97, 116, 
105, 111, 110, 32, 105, 110, 32, 116, 104, 101, 32, 109, 97, 105, 110, 32, 114, 101, 112, 111, 
32, 115, 99, 104, 101, 109, 97, 32, 98, 121, 32, 116, 104, 101, 10, 45, 45, 32, 34, 114, 
101, 98, 117, 105, 108, 100, 34, 32, 111, 112, 101, 114, 97, 116, 105, 111, 110, 46, 10, 10, 
45, 45, 32, 70, 105, 108, 101, 110, 97, 109, 101, 115, 10, 45, 45, 10, 67, 82, 69, 65, 
84, 69, 32, 84, 65, 66, 76, 69, 32, 114, 101, 112, 111, 46, 102, 105, 108, 101, 110, 97, 
109, 101, 40, 10, 32, 32, 102, 110, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 32, 80, 
82, 73, 77, 65, 82, 89, 32, 75, 69, 89, 44, 32, 32, 32, 32, 45, 45, 32, 70, 105, 
108, 101, 110, 97, 109, 101, 32, 73, 68, 10, 32, 32, 110, 97, 109, 101, 32, 84, 69, 88, 
84, 32, 85, 78, 73, 81, 85, 69, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 45, 45, 32, 78, 97, 109, 101, 32, 111, 102, 32, 102, 105, 108, 101, 32, 112, 97, 103, 
101, 10, 41, 59, 10, 10, 45, 45, 32, 76, 105, 110, 107, 97, 103, 101, 115, 32, 98, 101, 
116, 119, 101, 101, 110, 32, 99, 104, 101, 99, 107, 45, 105, 110, 115, 44, 32, 102, 105, 108, 
101, 115, 32, 99, 114, 101, 97, 116, 101, 100, 32, 98, 121, 32, 101, 97, 99, 104, 32, 99, 
104, 101, 99, 107, 45, 105, 110, 44, 32, 97, 110, 100, 10, 45, 45, 32, 116, 104, 101, 32, 
110, 97, 109, 101, 115, 32, 111, 102, 32, 116, 104, 111, 115, 101, 32, 102, 105, 108, 101, 115, 
46, 10, 45, 45, 10, 45, 45, 32, 69, 97, 99, 104, 32, 101, 110, 116, 114, 121, 32, 114, 
101, 112, 114, 101, 115, 101, 110, 116, 115, 32, 97, 32, 102, 105, 108, 101, 32, 116, 104, 97, 
116, 32, 99, 104, 97, 110, 103, 101, 100, 32, 99, 111, 110, 116, 101, 110, 116, 32, 102, 114, 
111, 109, 32, 112, 105, 100, 32, 116, 111, 32, 102, 105, 100, 10, 45, 45, 32, 100, 117, 101, 
32, 116, 111, 32, 116, 104, 101, 32, 99, 104, 101, 99, 107, 45, 105, 110, 32, 116, 104, 97, 
116, 32, 103, 111, 101, 115, 32, 102, 114, 111, 109, 32, 112, 109, 105, 100, 32, 116, 111, 32, 
109, 105, 100, 46, 32, 32, 102, 110, 105, 100, 32, 105, 115, 32, 116, 104, 101, 32, 110, 97, 
109, 101, 10, 45, 45, 32, 111, 102, 32, 116, 104, 101, 32, 102, 105, 108, 101, 32, 105, 110, 
32, 116, 104, 101, 32, 109, 105, 100, 32, 99, 104, 101, 99, 107, 45, 105, 110, 46, 32, 32, 
73, 102, 32, 116, 104, 101, 32, 102, 105, 108, 101, 32, 119, 97, 115, 32, 114, 101, 110, 97, 
109, 101, 100, 32, 97, 115, 32, 112, 97, 114, 116, 10, 45, 45, 32, 111, 102, 32, 116, 104, 
101, 32, 109, 105, 100, 32, 99, 104, 101, 99, 107, 45, 105, 110, 44, 32, 116, 104, 101, 110, 
32, 112, 102, 110, 105, 100, 32, 105, 115, 32, 116, 104, 101, 32, 112, 114, 101, 118, 105, 111, 
117, 115, 32, 102, 105, 108, 101, 110, 97, 109, 101, 46, 10, 45, 45, 10, 45, 45, 32, 84, 
104, 101, 114, 101, 32, 99, 97, 110, 32, 98, 101, 32, 109, 117, 108, 116, 105, 112, 108, 101, 
32, 101, 110, 116, 114, 105, 101, 115, 32, 102, 111, 114, 32, 40, 109, 105, 100, 44, 102, 105, 
100, 41, 32, 105, 102, 32, 116, 104, 101, 32, 109, 105, 100, 32, 99, 104, 101, 99, 107, 45, 
105, 110, 32, 119, 97, 115, 10, 45, 45, 32, 97, 32, 109, 101, 114, 103, 101, 46, 32, 32, 
69, 110, 116, 114, 105, 101, 115, 32, 119, 105, 116, 104, 32, 105, 115, 97, 117, 120, 61, 61, 
48, 32, 97, 114, 101, 32, 102, 114, 111, 109, 32, 116, 104, 101, 32, 112, 114, 105, 109, 97, 
114, 121, 32, 112, 97, 114, 101, 110, 116, 46, 32, 32, 77, 101, 114, 103, 101, 10, 45, 45, 
32, 112, 97, 114, 101, 110, 116, 115, 32, 104, 97, 118, 101, 32, 105, 115, 97, 117, 120, 32, 
115, 101, 116, 32, 116, 111, 32, 116, 114, 117, 101, 46, 10, 45, 45, 10, 45, 45, 32, 70, 
105, 101, 108, 100, 32, 110, 97, 109, 101, 32, 109, 110, 101, 109, 111, 110, 105, 99, 115, 58, 
10, 45, 45, 32, 32, 32, 32, 109, 105, 100, 32, 61, 32, 77, 97, 110, 105, 102, 101, 115, 
116, 32, 73, 68, 46, 32, 32, 40, 69, 97, 99, 104, 32, 99, 104, 101, 99, 107, 45, 105, 
110, 32, 105, 115, 32, 115, 116, 111, 114, 101, 100, 32, 97, 115, 32, 97, 32, 34, 77, 97, 
110, 105, 102, 101, 115, 116, 34, 41, 10, 45, 45, 32, 32, 32, 32, 102, 105, 100, 32, 61, 
32, 70, 105, 108, 101, 32, 73, 68, 46, 10, 45, 45, 32, 32, 32, 32, 112, 109, 105, 100, 
32, 61, 32, 80, 97, 114, 101, 110, 116, 32, 77, 97, 110, 105, 102, 101, 115, 116, 32, 73, 
68, 46, 10, 45, 45, 32, 32, 32, 32, 112, 105, 100, 32, 61, 32, 80, 97, 114, 101, 110, 
116, 32, 102, 105, 108, 101, 32, 73, 68, 46, 10, 45, 45, 32, 32, 32, 32, 102, 110, 105, 
100, 32, 61, 32, 70, 105, 108, 101, 32, 78, 97, 109, 101, 32, 73, 68, 46, 10, 45, 45, 
32, 32, 32, 32, 112, 102, 110, 105, 100, 32, 61, 32, 80, 97, 114, 101, 110, 116, 32, 70, 
105, 108, 101, 32, 78, 97, 109, 101, 32, 73, 68, 46, 10, 45, 45, 32, 32, 32, 32, 105, 
115, 97, 117, 120, 32, 61, 32, 112, 109, 105, 100, 32, 73, 83, 32, 65, 85, 88, 105, 108, 
105, 97, 114, 121, 32, 112, 97, 114, 101, 110, 116, 44, 32, 110, 111, 116, 32, 112, 114, 105, 
109, 97, 114, 121, 32, 112, 97, 114, 101, 110, 116, 10, 45, 45, 10, 45, 45, 32, 112, 105, 
100, 61, 61, 48, 32, 32, 32, 32, 105, 102, 32, 116, 104, 101, 32, 102, 105, 108, 101, 32, 
105, 115, 32, 97, 100, 100, 101, 100, 32, 98, 121, 32, 99, 104, 101, 99, 107, 45, 105, 110, 
32, 109, 105, 100, 46, 10, 45, 45, 32, 112, 105, 100, 61, 61, 40, 45, 49, 41, 32, 105, 
102, 32, 116, 104, 101, 32, 102, 105, 108, 101, 32, 101, 120, 105, 115, 116, 115, 32, 105, 110, 
32, 97, 32, 109, 101, 114, 103, 101, 32, 112, 97, 114, 101, 110, 116, 115, 32, 98, 117, 116, 
32, 110, 111, 116, 32, 105, 110, 32, 116, 104, 101, 32, 112, 114, 105, 109, 97, 114, 121, 10, 
45, 45, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 114, 101, 110, 116, 46, 
32, 32, 73, 110, 32, 111, 116, 104, 101, 114, 32, 119, 111, 114, 100, 115, 44, 32, 105, 102, 
32, 116, 104, 101, 32, 102, 105, 108, 101, 32, 102, 105, 108, 101, 32, 119, 97, 115, 32, 97, 
100, 100, 101, 100, 32, 98, 121, 32, 109, 101, 114, 103, 101, 46, 10, 45, 45, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 40, 84, 79, 68, 79, 58, 32, 99, 111, 110, 102, 105, 
114, 109, 32, 105, 102, 47, 119, 104, 101, 114, 101, 32, 116, 104, 105, 115, 32, 105, 115, 32, 
117, 115, 101, 100, 32, 105, 110, 32, 102, 111, 115, 115, 105, 108, 32, 97, 110, 100, 32, 116, 
104, 101, 110, 32, 109, 97, 107, 101, 32, 115, 117, 114, 101, 10, 45, 45, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 108, 105, 98, 102, 111, 115, 115, 105, 108, 32, 100, 111, 101, 
115, 32, 115, 111, 44, 32, 116, 111, 111, 46, 41, 10, 45, 45, 32, 102, 105, 100, 61, 61, 
48, 32, 32, 32, 32, 105, 102, 32, 116, 104, 101, 32, 102, 105, 108, 101, 32, 105, 115, 32, 
114, 101, 109, 111, 118, 101, 100, 32, 98, 121, 32, 99, 104, 101, 99, 107, 45, 105, 110, 32, 
109, 105, 100, 46, 10, 45, 45, 10, 67, 82, 69, 65, 84, 69, 32, 84, 65, 66, 76, 69, 
32, 114, 101, 112, 111, 46, 109, 108, 105, 110, 107, 40, 10, 32, 32, 109, 105, 100, 32, 73, 
78, 84, 69, 71, 69, 82, 44, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 67, 104, 
101, 99, 107, 45, 105, 110, 32, 116, 104, 97, 116, 32, 99, 111, 110, 116, 97, 105, 110, 115, 
32, 102, 105, 100, 10, 32, 32, 102, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 44, 32, 
32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 78, 101, 119, 32, 102, 105, 108, 101, 32, 99, 
111, 110, 116, 101, 110, 116, 32, 82, 73, 68, 46, 32, 48, 32, 105, 102, 32, 100, 101, 108, 
101, 116, 101, 100, 10, 32, 32, 112, 109, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 44, 
32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 67, 104, 101, 99, 107, 45, 105, 110, 32, 82, 
73, 68, 32, 116, 104, 97, 116, 32, 99, 111, 110, 116, 97, 105, 110, 115, 32, 112, 105, 100, 
10, 32, 32, 112, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 44, 32, 32, 32, 32, 32, 
32, 32, 32, 45, 45, 32, 80, 114, 101, 118, 32, 102, 105, 108, 101, 32, 99, 111, 110, 116, 
101, 110, 116, 32, 82, 73, 68, 46, 32, 48, 32, 105, 102, 32, 110, 101, 119, 46, 32, 45, 
49, 32, 105, 102, 32, 102, 114, 111, 109, 32, 97, 32, 109, 101, 114, 103, 101, 10, 32, 32, 
102, 110, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 32, 82, 69, 70, 69, 82, 69, 78, 
67, 69, 83, 32, 102, 105, 108, 101, 110, 97, 109, 101, 44, 32, 32, 32, 45, 45, 32, 78, 
97, 109, 101, 32, 111, 102, 32, 116, 104, 101, 32, 102, 105, 108, 101, 10, 32, 32, 112, 102, 
110, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 44, 32, 32, 32, 32, 32, 32, 45, 45, 
32, 80, 114, 101, 118, 105, 111, 117, 115, 32, 110, 97, 109, 101, 46, 32, 48, 32, 105, 102, 
32, 117, 110, 99, 104, 97, 110, 103, 101, 100, 10, 32, 32, 109, 112, 101, 114, 109, 32, 73, 
78, 84, 69, 71, 69, 82, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 70, 105, 108, 101, 32, 112, 101, 114, 
109, 105, 115, 115, 105, 111, 110, 115, 46, 32, 32, 49, 61, 61, 101, 120, 101, 99, 10, 32, 
32, 105, 115, 97, 117, 120, 32, 66, 79, 79, 76, 69, 65, 78, 32, 68, 69, 70, 65, 85, 
76, 84, 32, 48, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 
84, 82, 85, 69, 32, 105, 102, 32, 112, 109, 105, 100, 32, 105, 115, 32, 116, 104, 101, 32, 
112, 114, 105, 109, 97, 114, 121, 10, 41, 59, 10, 67, 82, 69, 65, 84, 69, 32, 73, 78, 
68, 69, 88, 32, 114, 101, 112, 111, 46, 109, 108, 105, 110, 107, 95, 105, 49, 32, 79, 78, 
32, 109, 108, 105, 110, 107, 40, 109, 105, 100, 41, 59, 10, 67, 82, 69, 65, 84, 69, 32, 
73, 78, 68, 69, 88, 32, 114, 101, 112, 111, 46, 109, 108, 105, 110, 107, 95, 105, 50, 32, 
79, 78, 32, 109, 108, 105, 110, 107, 40, 102, 110, 105, 100, 41, 59, 10, 67, 82, 69, 65, 
84, 69, 32, 73, 78, 68, 69, 88, 32, 114, 101, 112, 111, 46, 109, 108, 105, 110, 107, 95, 
105, 51, 32, 79, 78, 32, 109, 108, 105, 110, 107, 40, 102, 105, 100, 41, 59, 10, 67, 82, 
69, 65, 84, 69, 32, 73, 78, 68, 69, 88, 32, 114, 101, 112, 111, 46, 109, 108, 105, 110, 
107, 95, 105, 52, 32, 79, 78, 32, 109, 108, 105, 110, 107, 40, 112, 105, 100, 41, 59, 10, 
10, 45, 45, 32, 80, 97, 114, 101, 110, 116, 47, 99, 104, 105, 108, 100, 32, 108, 105, 110, 
107, 97, 103, 101, 115, 32, 98, 101, 116, 119, 101, 101, 110, 32, 99, 104, 101, 99, 107, 105, 
110, 115, 10, 45, 45, 10, 67, 82, 69, 65, 84, 69, 32, 84, 65, 66, 76, 69, 32, 114, 
101, 112, 111, 46, 112, 108, 105, 110, 107, 40, 10, 32, 32, 112, 105, 100, 32, 73, 78, 84, 
69, 71, 69, 82, 32, 82, 69, 70, 69, 82, 69, 78, 67, 69, 83, 32, 98, 108, 111, 98, 
44, 32, 32, 32, 32, 45, 45, 32, 80, 97, 114, 101, 110, 116, 32, 109, 97, 110, 105, 102, 
101, 115, 116, 10, 32, 32, 99, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 32, 82, 69, 
70, 69, 82, 69, 78, 67, 69, 83, 32, 98, 108, 111, 98, 44, 32, 32, 32, 32, 45, 45, 
32, 67, 104, 105, 108, 100, 32, 109, 97, 110, 105, 102, 101, 115, 116, 10, 32, 32, 105, 115, 
112, 114, 105, 109, 32, 66, 79, 79, 76, 69, 65, 78, 44, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 112, 105, 100, 32, 105, 115, 32, 
116, 104, 101, 32, 112, 114, 105, 109, 97, 114, 121, 32, 112, 97, 114, 101, 110, 116, 32, 111, 
102, 32, 99, 105, 100, 10, 32, 32, 109, 116, 105, 109, 101, 32, 68, 65, 84, 69, 84, 73, 
77, 69, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
45, 45, 32, 116, 104, 101, 32, 100, 97, 116, 101, 47, 116, 105, 109, 101, 32, 115, 116, 97, 
109, 112, 32, 111, 110, 32, 99, 105, 100, 46, 32, 32, 74, 117, 108, 105, 97, 110, 32, 100, 
97, 121, 46, 10, 32, 32, 98, 97, 115, 101, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 
32, 82, 69, 70, 69, 82, 69, 78, 67, 69, 83, 32, 98, 108, 111, 98, 44, 32, 45, 45, 
32, 66, 97, 115, 101, 108, 105, 110, 101, 32, 105, 102, 32, 99, 105, 100, 32, 105, 115, 32, 
97, 32, 100, 101, 108, 116, 97, 32, 109, 97, 110, 105, 102, 101, 115, 116, 46, 10, 32, 32, 
85, 78, 73, 81, 85, 69, 40, 112, 105, 100, 44, 32, 99, 105, 100, 41, 10, 41, 59, 10, 
67, 82, 69, 65, 84, 69, 32, 73, 78, 68, 69, 88, 32, 114, 101, 112, 111, 46, 112, 108, 
105, 110, 107, 95, 105, 50, 32, 79, 78, 32, 112, 108, 105, 110, 107, 40, 99, 105, 100, 44, 
112, 105, 100, 41, 59, 10, 10, 45, 45, 32, 65, 32, 34, 108, 101, 97, 102, 34, 32, 99, 
104, 101, 99, 107, 105, 110, 32, 105, 115, 32, 97, 32, 99, 104, 101, 99, 107, 105, 110, 32, 
116, 104, 97, 116, 32, 104, 97, 115, 32, 110, 111, 32, 99, 104, 105, 108, 100, 114, 101, 110, 
32, 105, 110, 32, 116, 104, 101, 32, 115, 97, 109, 101, 10, 45, 45, 32, 98, 114, 97, 110, 
99, 104, 46, 32, 32, 84, 104, 101, 32, 115, 101, 116, 32, 111, 102, 32, 97, 108, 108, 32, 
108, 101, 97, 118, 101, 115, 32, 105, 115, 32, 101, 97, 115, 105, 108, 121, 32, 99, 111, 109, 
112, 117, 116, 101, 100, 32, 119, 105, 116, 104, 32, 97, 32, 106, 111, 105, 110, 44, 10, 45, 
45, 32, 98, 101, 116, 119, 101, 101, 110, 32, 116, 104, 101, 32, 112, 108, 105, 110, 107, 32, 
97, 110, 100, 32, 116, 97, 103, 120, 114, 101, 102, 32, 116, 97, 98, 108, 101, 115, 44, 32, 
98, 117, 116, 32, 105, 116, 32, 105, 115, 32, 97, 32, 115, 108, 111, 119, 101, 114, 32, 106, 
111, 105, 110, 32, 102, 111, 114, 10, 45, 45, 32, 118, 101, 114, 121, 32, 108, 97, 114, 103, 
101, 32, 114, 101, 112, 111, 115, 105, 116, 111, 114, 105, 101, 115, 32, 40, 114, 101, 112, 111, 
115, 105, 116, 111, 114, 105, 101, 115, 32, 119, 105, 116, 104, 32, 49, 48, 48, 44, 48, 48, 
48, 32, 111, 114, 32, 109, 111, 114, 101, 32, 99, 104, 101, 99, 107, 105, 110, 115, 41, 10, 
45, 45, 32, 97, 110, 100, 32, 115, 111, 32, 105, 116, 32, 109, 97, 107, 101, 115, 32, 115, 
101, 110, 115, 101, 32, 116, 111, 32, 112, 114, 101, 99, 111, 109, 112, 117, 116, 101, 32, 116, 
104, 101, 32, 115, 101, 116, 32, 111, 102, 32, 108, 101, 97, 118, 101, 115, 46, 32, 32, 84, 
104, 101, 114, 101, 32, 105, 115, 10, 45, 45, 32, 111, 110, 101, 32, 101, 110, 116, 114, 121, 
32, 105, 110, 32, 116, 104, 101, 32, 102, 111, 108, 108, 111, 119, 105, 110, 103, 32, 116, 97, 
98, 108, 101, 32, 102, 111, 114, 32, 101, 97, 99, 104, 32, 108, 101, 97, 102, 46, 10, 45, 
45, 10, 67, 82, 69, 65, 84, 69, 32, 84, 65, 66, 76, 69, 32, 114, 101, 112, 111, 46, 
108, 101, 97, 102, 40, 114, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 32, 80, 82, 73, 
77, 65, 82, 89, 32, 75, 69, 89, 41, 59, 10, 10, 45, 45, 32, 69, 118, 101, 110, 116, 
115, 32, 117, 115, 101, 100, 32, 116, 111, 32, 103, 101, 110, 101, 114, 97, 116, 101, 32, 97, 
32, 116, 105, 109, 101, 108, 105, 110, 101, 10, 45, 45, 10, 67, 82, 69, 65, 84, 69, 32, 
84, 65, 66, 76, 69, 32, 114, 101, 112, 111, 46, 101, 118, 101, 110, 116, 40, 10, 32, 32, 
116, 121, 112, 101, 32, 84, 69, 88, 84, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 84, 121, 112, 101, 32, 
111, 102, 32, 101, 118, 101, 110, 116, 58, 32, 39, 99, 105, 39, 44, 32, 39, 119, 39, 44, 
32, 39, 101, 39, 44, 32, 39, 116, 39, 44, 32, 39, 103, 39, 10, 32, 32, 109, 116, 105, 
109, 101, 32, 68, 65, 84, 69, 84, 73, 77, 69, 44, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 84, 105, 109, 101, 32, 111, 102, 32, 
111, 99, 99, 117, 114, 114, 101, 110, 99, 101, 46, 32, 74, 117, 108, 105, 97, 110, 32, 100, 
97, 121, 46, 10, 32, 32, 111, 98, 106, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 32, 
80, 82, 73, 77, 65, 82, 89, 32, 75, 69, 89, 44, 32, 32, 32, 32, 32, 32, 45, 45, 
32, 65, 115, 115, 111, 99, 105, 97, 116, 101, 100, 32, 114, 101, 99, 111, 114, 100, 32, 73, 
68, 10, 32, 32, 116, 97, 103, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 44, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 65, 
115, 115, 111, 99, 105, 97, 116, 101, 100, 32, 116, 105, 99, 107, 101, 116, 32, 111, 114, 32, 
119, 105, 107, 105, 32, 110, 97, 109, 101, 32, 116, 97, 103, 10, 32, 32, 117, 105, 100, 32, 
73, 78, 84, 69, 71, 69, 82, 32, 82, 69, 70, 69, 82, 69, 78, 67, 69, 83, 32, 117, 
115, 101, 114, 44, 32, 32, 32, 32, 45, 45, 32, 85, 115, 101, 114, 32, 119, 104, 111, 32, 
99, 97, 117, 115, 101, 100, 32, 116, 104, 101, 32, 101, 118, 101, 110, 116, 10, 32, 32, 98, 
103, 99, 111, 108, 111, 114, 32, 84, 69, 88, 84, 44, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 67, 111, 108, 111, 114, 32, 
115, 101, 116, 32, 98, 121, 32, 39, 98, 103, 99, 111, 108, 111, 114, 39, 32, 112, 114, 111, 
112, 101, 114, 116, 121, 10, 32, 32, 101, 117, 115, 101, 114, 32, 84, 69, 88, 84, 44, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
45, 45, 32, 85, 115, 101, 114, 32, 115, 101, 116, 32, 98, 121, 32, 39, 117, 115, 101, 114, 
39, 32, 112, 114, 111, 112, 101, 114, 116, 121, 10, 32, 32, 117, 115, 101, 114, 32, 84, 69, 
88, 84, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 45, 45, 32, 78, 97, 109, 101, 32, 111, 102, 32, 116, 104, 101, 32, 
117, 115, 101, 114, 10, 32, 32, 101, 99, 111, 109, 109, 101, 110, 116, 32, 84, 69, 88, 84, 
44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 
45, 32, 67, 111, 109, 109, 101, 110, 116, 32, 115, 101, 116, 32, 98, 121, 32, 39, 99, 111, 
109, 109, 101, 110, 116, 39, 32, 112, 114, 111, 112, 101, 114, 116, 121, 10, 32, 32, 99, 111, 
109, 109, 101, 110, 116, 32, 84, 69, 88, 84, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 67, 111, 109, 109, 101, 110, 116, 
32, 100, 101, 115, 99, 114, 105, 98, 105, 110, 103, 32, 116, 104, 101, 32, 101, 118, 101, 110, 
116, 10, 32, 32, 98, 114, 105, 101, 102, 32, 84, 69, 88, 84, 44, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 83, 
104, 111, 114, 116, 32, 99, 111, 109, 109, 101, 110, 116, 32, 119, 104, 101, 110, 32, 116, 97, 
103, 105, 100, 32, 97, 108, 114, 101, 97, 100, 121, 32, 115, 101, 101, 110, 10, 32, 32, 111, 
109, 116, 105, 109, 101, 32, 68, 65, 84, 69, 84, 73, 77, 69, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 79, 114, 105, 103, 105, 110, 
97, 108, 32, 117, 110, 99, 104, 97, 110, 103, 101, 100, 32, 100, 97, 116, 101, 43, 116, 105, 
109, 101, 44, 32, 111, 114, 32, 78, 85, 76, 76, 10, 41, 59, 10, 67, 82, 69, 65, 84, 
69, 32, 73, 78, 68, 69, 88, 32, 114, 101, 112, 111, 46, 101, 118, 101, 110, 116, 95, 105, 
49, 32, 79, 78, 32, 101, 118, 101, 110, 116, 40, 109, 116, 105, 109, 101, 41, 59, 10, 10, 
45, 45, 32, 65, 32, 114, 101, 99, 111, 114, 100, 32, 111, 102, 32, 112, 104, 97, 110, 116, 
111, 109, 115, 46, 32, 32, 65, 32, 112, 104, 97, 110, 116, 111, 109, 32, 105, 115, 32, 97, 
32, 114, 101, 99, 111, 114, 100, 32, 102, 111, 114, 32, 119, 104, 105, 99, 104, 32, 119, 101, 
32, 107, 110, 111, 119, 32, 116, 104, 101, 10, 45, 45, 32, 85, 85, 73, 68, 32, 98, 117, 
116, 32, 119, 101, 32, 100, 111, 32, 110, 111, 116, 32, 40, 121, 101, 116, 41, 32, 107, 110, 
111, 119, 32, 116, 104, 101, 32, 102, 105, 108, 101, 32, 99, 111, 110, 116, 101, 110, 116, 46, 
10, 45, 45, 10, 67, 82, 69, 65, 84, 69, 32, 84, 65, 66, 76, 69, 32, 114, 101, 112, 
111, 46, 112, 104, 97, 110, 116, 111, 109, 40, 10, 32, 32, 114, 105, 100, 32, 73, 78, 84, 
69, 71, 69, 82, 32, 80, 82, 73, 77, 65, 82, 89, 32, 75, 69, 89, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 45, 45, 32, 82, 101, 99, 111, 114, 100, 32, 73, 68, 32, 111, 102, 
32, 116, 104, 101, 32, 112, 104, 97, 110, 116, 111, 109, 10, 41, 59, 10, 10, 45, 45, 32, 
65, 32, 114, 101, 99, 111, 114, 100, 32, 111, 102, 32, 111, 114, 112, 104, 97, 110, 101, 100, 
32, 100, 101, 108, 116, 97, 45, 109, 97, 110, 105, 102, 101, 115, 116, 115, 46, 32, 32, 65, 
110, 32, 111, 114, 112, 104, 97, 110, 32, 105, 115, 32, 97, 32, 100, 101, 108, 116, 97, 45, 
109, 97, 110, 105, 102, 101, 115, 116, 10, 45, 45, 32, 102, 111, 114, 32, 119, 104, 105, 99, 
104, 32, 119, 101, 32, 104, 97, 118, 101, 32, 99, 111, 110, 116, 101, 110, 116, 44, 32, 98, 
117, 116, 32, 105, 116, 115, 32, 98, 97, 115, 101, 108, 105, 110, 101, 45, 109, 97, 110, 105, 
102, 101, 115, 116, 32, 105, 115, 32, 97, 32, 112, 104, 97, 110, 116, 111, 109, 46, 10, 45, 
45, 32, 87, 101, 32, 104, 97, 118, 101, 32, 116, 111, 32, 116, 114, 97, 99, 107, 32, 97, 
108, 108, 32, 111, 114, 112, 104, 97, 110, 32, 109, 97, 110, 105, 102, 101, 115, 116, 115, 32, 
115, 111, 32, 116, 104, 97, 116, 32, 119, 104, 101, 110, 32, 116, 104, 101, 32, 98, 97, 115, 
101, 108, 105, 110, 101, 32, 97, 114, 114, 105, 118, 101, 115, 44, 10, 45, 45, 32, 119, 101, 
32, 107, 110, 111, 119, 32, 116, 111, 32, 112, 114, 111, 99, 101, 115, 115, 32, 116, 104, 101, 
32, 111, 114, 112, 104, 97, 110, 101, 100, 32, 100, 101, 108, 116, 97, 115, 46, 10, 67, 82, 
69, 65, 84, 69, 32, 84, 65, 66, 76, 69, 32, 114, 101, 112, 111, 46, 111, 114, 112, 104, 
97, 110, 40, 10, 32, 32, 114, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 32, 80, 82, 
73, 77, 65, 82, 89, 32, 75, 69, 89, 44, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 
32, 68, 101, 108, 116, 97, 32, 109, 97, 110, 105, 102, 101, 115, 116, 32, 119, 105, 116, 104, 
32, 97, 32, 112, 104, 97, 110, 116, 111, 109, 32, 98, 97, 115, 101, 108, 105, 110, 101, 10, 
32, 32, 98, 97, 115, 101, 108, 105, 110, 101, 32, 73, 78, 84, 69, 71, 69, 82, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 80, 104, 97, 
110, 116, 111, 109, 32, 98, 97, 115, 101, 108, 105, 110, 101, 32, 111, 102, 32, 116, 104, 105, 
115, 32, 111, 114, 112, 104, 97, 110, 10, 41, 59, 10, 67, 82, 69, 65, 84, 69, 32, 73, 
78, 68, 69, 88, 32, 114, 101, 112, 111, 46, 111, 114, 112, 104, 97, 110, 95, 98, 97, 115, 
101, 108, 105, 110, 101, 32, 79, 78, 32, 111, 114, 112, 104, 97, 110, 40, 98, 97, 115, 101, 
108, 105, 110, 101, 41, 59, 10, 10, 45, 45, 32, 85, 110, 99, 108, 117, 115, 116, 101, 114, 
101, 100, 32, 114, 101, 99, 111, 114, 100, 115, 46, 32, 32, 65, 110, 32, 117, 110, 99, 108, 
117, 115, 116, 101, 114, 101, 100, 32, 114, 101, 99, 111, 114, 100, 32, 105, 115, 32, 97, 32, 
114, 101, 99, 111, 114, 100, 32, 40, 105, 110, 99, 108, 117, 100, 105, 110, 103, 10, 45, 45, 
32, 97, 32, 99, 108, 117, 115, 116, 101, 114, 32, 114, 101, 99, 111, 114, 100, 115, 32, 116, 
104, 101, 109, 115, 101, 108, 118, 101, 115, 41, 32, 116, 104, 97, 116, 32, 105, 115, 32, 110, 
111, 116, 32, 109, 101, 110, 116, 105, 111, 110, 101, 100, 32, 98, 121, 32, 115, 111, 109, 101, 
32, 111, 116, 104, 101, 114, 10, 45, 45, 32, 99, 108, 117, 115, 116, 101, 114, 46, 10, 45, 
45, 10, 45, 45, 32, 80, 104, 97, 110, 116, 111, 109, 115, 32, 97, 114, 101, 32, 117, 115, 
117, 97, 108, 108, 121, 32, 105, 110, 99, 108, 117, 100, 101, 100, 32, 105, 110, 32, 116, 104, 
101, 32, 117, 110, 99, 108, 117, 115, 116, 101, 114, 101, 100, 32, 116, 97, 98, 108, 101, 46, 
32, 32, 65, 32, 110, 101, 119, 32, 99, 108, 117, 115, 116, 101, 114, 10, 45, 45, 32, 119, 
105, 108, 108, 32, 110, 101, 118, 101, 114, 32, 98, 101, 32, 99, 114, 101, 97, 116, 101, 100, 
32, 116, 104, 97, 116, 32, 99, 111, 110, 116, 97, 105, 110, 115, 32, 97, 32, 112, 104, 97, 
110, 116, 111, 109, 46, 32, 32, 66, 117, 116, 32, 97, 110, 111, 116, 104, 101, 114, 32, 114, 
101, 112, 111, 115, 105, 116, 111, 114, 121, 10, 45, 45, 32, 109, 105, 103, 104, 116, 32, 115, 
101, 110, 100, 32, 117, 115, 32, 97, 32, 99, 108, 117, 115, 116, 101, 114, 32, 116, 104, 97, 
116, 32, 99, 111, 110, 116, 97, 105, 110, 115, 32, 101, 110, 116, 114, 105, 101, 115, 32, 116, 
104, 97, 116, 32, 97, 114, 101, 32, 112, 104, 97, 110, 116, 111, 109, 115, 32, 116, 111, 10, 
45, 45, 32, 117, 115, 46, 10, 45, 45, 10, 67, 82, 69, 65, 84, 69, 32, 84, 65, 66, 
76, 69, 32, 114, 101, 112, 111, 46, 117, 110, 99, 108, 117, 115, 116, 101, 114, 101, 100, 40, 
10, 32, 32, 114, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 32, 80, 82, 73, 77, 65, 
82, 89, 32, 75, 69, 89, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 82, 101, 
99, 111, 114, 100, 32, 73, 68, 32, 111, 102, 32, 116, 104, 101, 32, 117, 110, 99, 108, 117, 
115, 116, 101, 114, 101, 100, 32, 102, 105, 108, 101, 10, 41, 59, 10, 10, 45, 45, 32, 82, 
101, 99, 111, 114, 100, 115, 32, 119, 104, 105, 99, 104, 32, 104, 97, 118, 101, 32, 110, 101, 
118, 101, 114, 32, 98, 101, 101, 110, 32, 112, 117, 115, 104, 101, 100, 32, 116, 111, 32, 97, 
110, 111, 116, 104, 101, 114, 32, 115, 101, 114, 118, 101, 114, 46, 32, 32, 84, 104, 105, 115, 
32, 105, 115, 10, 45, 45, 32, 117, 115, 101, 100, 32, 116, 111, 32, 114, 101, 100, 117, 99, 
101, 32, 112, 117, 115, 104, 32, 111, 112, 101, 114, 97, 116, 105, 111, 110, 115, 32, 116, 111, 
32, 97, 32, 115, 105, 110, 103, 108, 101, 32, 72, 84, 84, 80, 32, 114, 101, 113, 117, 101, 
115, 116, 32, 105, 110, 32, 116, 104, 101, 10, 45, 45, 32, 99, 111, 109, 109, 111, 110, 32, 
99, 97, 115, 101, 32, 119, 104, 101, 110, 32, 111, 110, 101, 32, 114, 101, 112, 111, 115, 105, 
116, 111, 114, 121, 32, 111, 110, 108, 121, 32, 116, 97, 108, 107, 115, 32, 116, 111, 32, 97, 
32, 115, 105, 110, 103, 108, 101, 32, 115, 101, 114, 118, 101, 114, 46, 10, 45, 45, 10, 67, 
82, 69, 65, 84, 69, 32, 84, 65, 66, 76, 69, 32, 114, 101, 112, 111, 46, 117, 110, 115, 
101, 110, 116, 40, 10, 32, 32, 114, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 32, 80, 
82, 73, 77, 65, 82, 89, 32, 75, 69, 89, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 
45, 32, 82, 101, 99, 111, 114, 100, 32, 73, 68, 32, 111, 102, 32, 116, 104, 101, 32, 112, 
104, 97, 110, 116, 111, 109, 10, 41, 59, 10, 10, 45, 45, 32, 69, 97, 99, 104, 32, 98, 
97, 115, 101, 108, 105, 110, 101, 32, 111, 114, 32, 109, 97, 110, 105, 102, 101, 115, 116, 32, 
99, 97, 110, 32, 104, 97, 118, 101, 32, 111, 110, 101, 32, 111, 114, 32, 109, 111, 114, 101, 
32, 116, 97, 103, 115, 46, 32, 32, 65, 32, 116, 97, 103, 10, 45, 45, 32, 105, 115, 32, 
100, 101, 102, 105, 110, 101, 100, 32, 98, 121, 32, 97, 32, 114, 111, 119, 32, 105, 110, 32, 
116, 104, 101, 32, 110, 101, 120, 116, 32, 116, 97, 98, 108, 101, 46, 10, 45, 45, 32, 10, 
45, 45, 32, 87, 105, 107, 105, 32, 112, 97, 103, 101, 115, 32, 97, 114, 101, 32, 116, 97, 
103, 103, 101, 100, 32, 119, 105, 116, 104, 32, 34, 119, 105, 107, 105, 45, 78, 65, 77, 69, 
34, 32, 119, 104, 101, 114, 101, 32, 78, 65, 77, 69, 32, 105, 115, 32, 116, 104, 101, 32, 
110, 97, 109, 101, 32, 111, 102, 10, 45, 45, 32, 116, 104, 101, 32, 119, 105, 107, 105, 32, 
112, 97, 103, 101, 46, 32, 32, 84, 105, 99, 107, 101, 116, 115, 32, 99, 104, 97, 110, 103, 
101, 115, 32, 97, 114, 101, 32, 116, 97, 103, 103, 101, 100, 32, 119, 105, 116, 104, 32, 34, 
116, 105, 99, 107, 101, 116, 45, 85, 85, 73, 68, 34, 32, 119, 104, 101, 114, 101, 32, 10, 
45, 45, 32, 85, 85, 73, 68, 32, 105, 115, 32, 116, 104, 101, 32, 105, 110, 100, 101, 110, 
116, 105, 102, 105, 101, 114, 32, 111, 102, 32, 116, 104, 101, 32, 116, 105, 99, 107, 101, 116, 
46, 32, 32, 84, 97, 103, 115, 32, 117, 115, 101, 100, 32, 116, 111, 32, 97, 115, 115, 105, 
103, 110, 32, 115, 121, 109, 98, 111, 108, 105, 99, 10, 45, 45, 32, 110, 97, 109, 101, 115, 
32, 116, 111, 32, 98, 97, 115, 101, 108, 105, 110, 101, 115, 32, 97, 114, 101, 32, 98, 114, 
97, 110, 99, 104, 101, 115, 32, 97, 114, 101, 32, 111, 102, 32, 116, 104, 101, 32, 102, 111, 
114, 109, 32, 34, 115, 121, 109, 45, 78, 65, 77, 69, 34, 32, 119, 104, 101, 114, 101, 10, 
45, 45, 32, 78, 65, 77, 69, 32, 105, 115, 32, 116, 104, 101, 32, 115, 121, 109, 98, 111, 
108, 105, 99, 32, 110, 97, 109, 101, 46, 10, 45, 45, 10, 67, 82, 69, 65, 84, 69, 32, 
84, 65, 66, 76, 69, 32, 114, 101, 112, 111, 46, 116, 97, 103, 40, 10, 32, 32, 116, 97, 
103, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 32, 80, 82, 73, 77, 65, 82, 89, 32, 
75, 69, 89, 44, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 78, 117, 109, 101, 114, 105, 
99, 32, 116, 97, 103, 32, 73, 68, 10, 32, 32, 116, 97, 103, 110, 97, 109, 101, 32, 84, 
69, 88, 84, 32, 85, 78, 73, 81, 85, 69, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 45, 45, 32, 84, 97, 103, 32, 110, 97, 109, 101, 46, 10, 41, 59, 10, 
73, 78, 83, 69, 82, 84, 32, 73, 78, 84, 79, 32, 114, 101, 112, 111, 46, 116, 97, 103, 
32, 86, 65, 76, 85, 69, 83, 40, 49, 44, 32, 39, 98, 103, 99, 111, 108, 111, 114, 39, 
41, 59, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 70, 83, 76, 95, 84, 65, 
71, 73, 68, 95, 66, 71, 67, 79, 76, 79, 82, 10, 73, 78, 83, 69, 82, 84, 32, 73, 
78, 84, 79, 32, 114, 101, 112, 111, 46, 116, 97, 103, 32, 86, 65, 76, 85, 69, 83, 40, 
50, 44, 32, 39, 99, 111, 109, 109, 101, 110, 116, 39, 41, 59, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 45, 45, 32, 70, 83, 76, 95, 84, 65, 71, 73, 68, 95, 67, 79, 77, 77, 
69, 78, 84, 10, 73, 78, 83, 69, 82, 84, 32, 73, 78, 84, 79, 32, 114, 101, 112, 111, 
46, 116, 97, 103, 32, 86, 65, 76, 85, 69, 83, 40, 51, 44, 32, 39, 117, 115, 101, 114, 
39, 41, 59, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 70, 83, 
76, 95, 84, 65, 71, 73, 68, 95, 85, 83, 69, 82, 10, 73, 78, 83, 69, 82, 84, 32, 
73, 78, 84, 79, 32, 114, 101, 112, 111, 46, 116, 97, 103, 32, 86, 65, 76, 85, 69, 83, 
40, 52, 44, 32, 39, 100, 97, 116, 101, 39, 41, 59, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 45, 45, 32, 70, 83, 76, 95, 84, 65, 71, 73, 68, 95, 68, 65, 84, 
69, 10, 73, 78, 83, 69, 82, 84, 32, 73, 78, 84, 79, 32, 114, 101, 112, 111, 46, 116, 
97, 103, 32, 86, 65, 76, 85, 69, 83, 40, 53, 44, 32, 39, 104, 105, 100, 100, 101, 110, 
39, 41, 59, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 70, 83, 76, 95, 
84, 65, 71, 73, 68, 95, 72, 73, 68, 68, 69, 78, 10, 73, 78, 83, 69, 82, 84, 32, 
73, 78, 84, 79, 32, 114, 101, 112, 111, 46, 116, 97, 103, 32, 86, 65, 76, 85, 69, 83, 
40, 54, 44, 32, 39, 112, 114, 105, 118, 97, 116, 101, 39, 41, 59, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 45, 45, 32, 70, 83, 76, 95, 84, 65, 71, 73, 68, 95, 80, 82, 73, 
86, 65, 84, 69, 10, 73, 78, 83, 69, 82, 84, 32, 73, 78, 84, 79, 32, 114, 101, 112, 
111, 46, 116, 97, 103, 32, 86, 65, 76, 85, 69, 83, 40, 55, 44, 32, 39, 99, 108, 117, 
115, 116, 101, 114, 39, 41, 59, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 70, 
83, 76, 95, 84, 65, 71, 73, 68, 95, 67, 76, 85, 83, 84, 69, 82, 10, 73, 78, 83, 
69, 82, 84, 32, 73, 78, 84, 79, 32, 114, 101, 112, 111, 46, 116, 97, 103, 32, 86, 65, 
76, 85, 69, 83, 40, 56, 44, 32, 39, 98, 114, 97, 110, 99, 104, 39, 41, 59, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 70, 83, 76, 95, 84, 65, 71, 73, 68, 
95, 66, 82, 65, 78, 67, 72, 10, 73, 78, 83, 69, 82, 84, 32, 73, 78, 84, 79, 32, 
114, 101, 112, 111, 46, 116, 97, 103, 32, 86, 65, 76, 85, 69, 83, 40, 57, 44, 32, 39, 
99, 108, 111, 115, 101, 100, 39, 41, 59, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 
45, 32, 70, 83, 76, 95, 84, 65, 71, 73, 68, 95, 67, 76, 79, 83, 69, 68, 10, 73, 
78, 83, 69, 82, 84, 32, 73, 78, 84, 79, 32, 114, 101, 112, 111, 46, 116, 97, 103, 32, 
86, 65, 76, 85, 69, 83, 40, 49, 48, 44, 39, 112, 97, 114, 101, 110, 116, 39, 41, 59, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 70, 83, 76, 95, 84, 65, 71, 
73, 68, 95, 80, 65, 82, 69, 78, 84, 10, 73, 78, 83, 69, 82, 84, 32, 73, 78, 84, 
79, 32, 114, 101, 112, 111, 46, 116, 97, 103, 32, 86, 65, 76, 85, 69, 83, 40, 49, 49, 
44, 39, 110, 111, 116, 101, 39, 41, 59, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 45, 45, 32, 70, 83, 76, 95, 84, 65, 71, 95, 78, 79, 84, 69, 10, 45, 45, 32, 
97, 114, 103, 117, 97, 98, 108, 101, 44, 32, 116, 111, 32, 102, 111, 114, 99, 101, 32, 97, 
117, 116, 111, 45, 105, 110, 99, 114, 101, 109, 101, 110, 116, 32, 116, 111, 32, 115, 116, 97, 
114, 116, 32, 97, 116, 32, 49, 48, 48, 58, 10, 45, 45, 32, 73, 78, 83, 69, 82, 84, 
32, 73, 78, 84, 79, 32, 116, 97, 103, 32, 86, 65, 76, 85, 69, 83, 40, 57, 57, 44, 
39, 70, 83, 76, 95, 84, 65, 71, 73, 68, 95, 77, 65, 88, 95, 73, 78, 84, 69, 82, 
78, 65, 76, 39, 41, 59, 10, 10, 45, 45, 32, 65, 115, 115, 105, 103, 110, 109, 101, 110, 
116, 115, 32, 111, 102, 32, 116, 97, 103, 115, 32, 116, 111, 32, 98, 97, 115, 101, 108, 105, 
110, 101, 115, 46, 32, 32, 78, 111, 116, 101, 32, 116, 104, 97, 116, 32, 119, 101, 32, 97, 
108, 108, 111, 119, 32, 116, 97, 103, 115, 32, 116, 111, 10, 45, 45, 32, 104, 97, 118, 101, 
32, 118, 97, 108, 117, 101, 115, 32, 97, 115, 115, 105, 103, 110, 101, 100, 32, 116, 111, 32, 
116, 104, 101, 109, 46, 32, 32, 83, 111, 32, 119, 101, 32, 97, 114, 101, 32, 110, 111, 116, 
32, 114, 101, 97, 108, 108, 121, 32, 100, 101, 97, 108, 105, 110, 103, 32, 119, 105, 116, 104, 
10, 45, 45, 32, 116, 97, 103, 115, 32, 104, 101, 114, 101, 46, 32, 32, 84, 104, 101, 115, 
101, 32, 97, 114, 101, 32, 114, 101, 97, 108, 108, 121, 32, 112, 114, 111, 112, 101, 114, 116, 
105, 101, 115, 46, 32, 32, 66, 117, 116, 32, 119, 101, 32, 97, 114, 101, 32, 103, 111, 105, 
110, 103, 32, 116, 111, 10, 45, 45, 32, 107, 101, 101, 112, 32, 99, 97, 108, 108, 105, 110, 
103, 32, 116, 104, 101, 109, 32, 116, 97, 103, 115, 32, 98, 101, 99, 97, 117, 115, 101, 32, 
105, 110, 32, 109, 97, 110, 121, 32, 99, 97, 115, 101, 115, 32, 116, 104, 101, 32, 118, 97, 
108, 117, 101, 32, 105, 115, 32, 105, 103, 110, 111, 114, 101, 100, 46, 10, 45, 45, 10, 67, 
82, 69, 65, 84, 69, 32, 84, 65, 66, 76, 69, 32, 114, 101, 112, 111, 46, 116, 97, 103, 
120, 114, 101, 102, 40, 10, 32, 32, 116, 97, 103, 105, 100, 32, 73, 78, 84, 69, 71, 69, 
82, 32, 82, 69, 70, 69, 82, 69, 78, 67, 69, 83, 32, 116, 97, 103, 44, 32, 32, 32, 
45, 45, 32, 84, 104, 101, 32, 116, 97, 103, 32, 116, 104, 97, 116, 32, 119, 97, 115, 32, 
97, 100, 100, 101, 100, 32, 111, 114, 32, 114, 101, 109, 111, 118, 101, 100, 10, 32, 32, 116, 
97, 103, 116, 121, 112, 101, 32, 73, 78, 84, 69, 71, 69, 82, 44, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 48, 58, 45, 44, 99, 97, 
110, 99, 101, 108, 32, 32, 49, 58, 43, 44, 115, 105, 110, 103, 108, 101, 32, 32, 50, 58, 
42, 44, 112, 114, 111, 112, 97, 103, 97, 116, 101, 10, 32, 32, 115, 114, 99, 105, 100, 32, 
73, 78, 84, 69, 71, 69, 82, 32, 82, 69, 70, 69, 82, 69, 78, 67, 69, 83, 32, 98, 
108, 111, 98, 44, 32, 32, 45, 45, 32, 65, 114, 116, 105, 102, 97, 99, 116, 32, 111, 102, 
32, 116, 97, 103, 46, 32, 48, 32, 102, 111, 114, 32, 112, 114, 111, 112, 97, 103, 97, 116, 
101, 100, 32, 116, 97, 103, 115, 10, 32, 32, 111, 114, 105, 103, 105, 100, 32, 73, 78, 84, 
69, 71, 69, 82, 32, 82, 69, 70, 69, 82, 69, 78, 67, 69, 83, 32, 98, 108, 111, 98, 
44, 32, 45, 45, 32, 99, 104, 101, 99, 107, 45, 105, 110, 32, 104, 111, 108, 100, 105, 110, 
103, 32, 112, 114, 111, 112, 97, 103, 97, 116, 101, 100, 32, 116, 97, 103, 10, 32, 32, 118, 
97, 108, 117, 101, 32, 84, 69, 88, 84, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 86, 97, 108, 117, 101, 32, 
111, 102, 32, 116, 104, 101, 32, 116, 97, 103, 46, 32, 32, 77, 105, 103, 104, 116, 32, 98, 
101, 32, 78, 85, 76, 76, 46, 10, 32, 32, 109, 116, 105, 109, 101, 32, 84, 73, 77, 69, 
83, 84, 65, 77, 80, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 32, 45, 45, 32, 84, 105, 109, 101, 32, 111, 102, 32, 97, 100, 100, 105, 116, 105, 111, 
110, 32, 111, 114, 32, 114, 101, 109, 111, 118, 97, 108, 46, 32, 74, 117, 108, 105, 97, 110, 
32, 100, 97, 121, 10, 32, 32, 114, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 32, 82, 
69, 70, 69, 82, 69, 78, 67, 69, 32, 98, 108, 111, 98, 44, 32, 32, 32, 32, 32, 45, 
45, 32, 65, 114, 116, 105, 102, 97, 99, 116, 32, 116, 97, 103, 32, 105, 115, 32, 97, 112, 
112, 108, 105, 101, 100, 32, 116, 111, 10, 32, 32, 85, 78, 73, 81, 85, 69, 40, 114, 105, 
100, 44, 32, 116, 97, 103, 105, 100, 41, 10, 41, 59, 10, 67, 82, 69, 65, 84, 69, 32, 
73, 78, 68, 69, 88, 32, 114, 101, 112, 111, 46, 116, 97, 103, 120, 114, 101, 102, 95, 105, 
49, 32, 79, 78, 32, 116, 97, 103, 120, 114, 101, 102, 40, 116, 97, 103, 105, 100, 44, 32, 
109, 116, 105, 109, 101, 41, 59, 10, 10, 45, 45, 32, 87, 104, 101, 110, 32, 97, 32, 104, 
121, 112, 101, 114, 108, 105, 110, 107, 32, 111, 99, 99, 117, 114, 115, 32, 102, 114, 111, 109, 
32, 111, 110, 101, 32, 97, 114, 116, 105, 102, 97, 99, 116, 32, 116, 111, 32, 97, 110, 111, 
116, 104, 101, 114, 32, 40, 102, 111, 114, 32, 101, 120, 97, 109, 112, 108, 101, 10, 45, 45, 
32, 119, 104, 101, 110, 32, 97, 32, 99, 104, 101, 99, 107, 45, 105, 110, 32, 99, 111, 109, 
109, 101, 110, 116, 32, 114, 101, 102, 101, 114, 115, 32, 116, 111, 32, 97, 32, 116, 105, 99, 
107, 101, 116, 41, 32, 97, 110, 32, 101, 110, 116, 114, 121, 32, 105, 115, 32, 109, 97, 100, 
101, 32, 105, 110, 10, 45, 45, 32, 116, 104, 101, 32, 102, 111, 108, 108, 111, 119, 105, 110, 
103, 32, 116, 97, 98, 108, 101, 32, 102, 111, 114, 32, 116, 104, 97, 116, 32, 104, 121, 112, 
101, 114, 108, 105, 110, 107, 46, 32, 32, 84, 104, 105, 115, 32, 116, 97, 98, 108, 101, 32, 
105, 115, 32, 117, 115, 101, 100, 32, 116, 111, 10, 45, 45, 32, 102, 97, 99, 105, 108, 105, 
116, 97, 116, 101, 32, 116, 104, 101, 32, 100, 105, 115, 112, 108, 97, 121, 32, 111, 102, 32, 
34, 98, 97, 99, 107, 32, 108, 105, 110, 107, 115, 34, 46, 10, 45, 45, 10, 67, 82, 69, 
65, 84, 69, 32, 84, 65, 66, 76, 69, 32, 114, 101, 112, 111, 46, 98, 97, 99, 107, 108, 
105, 110, 107, 40, 10, 32, 32, 116, 97, 114, 103, 101, 116, 32, 84, 69, 88, 84, 44, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 87, 104, 101, 114, 101, 32, 116, 
104, 101, 32, 104, 121, 112, 101, 114, 108, 105, 110, 107, 32, 112, 111, 105, 110, 116, 115, 32, 
116, 111, 10, 32, 32, 115, 114, 99, 116, 121, 112, 101, 32, 73, 78, 84, 44, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 48, 58, 32, 99, 104, 101, 99, 107, 45, 
105, 110, 32, 32, 49, 58, 32, 116, 105, 99, 107, 101, 116, 32, 32, 50, 58, 32, 119, 105, 
107, 105, 10, 32, 32, 115, 114, 99, 105, 100, 32, 73, 78, 84, 44, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 114, 105, 100, 32, 102, 111, 114, 32, 99, 
104, 101, 99, 107, 105, 110, 32, 111, 114, 32, 119, 105, 107, 105, 46, 32, 32, 116, 107, 116, 
95, 105, 100, 32, 102, 111, 114, 32, 116, 105, 99, 107, 101, 116, 46, 10, 32, 32, 109, 116, 
105, 109, 101, 32, 84, 73, 77, 69, 83, 84, 65, 77, 80, 44, 32, 32, 32, 32, 32, 32, 
32, 45, 45, 32, 116, 105, 109, 101, 32, 116, 104, 97, 116, 32, 116, 104, 101, 32, 104, 121, 
112, 101, 114, 108, 105, 110, 107, 32, 119, 97, 115, 32, 97, 100, 100, 101, 100, 46, 32, 74, 
117, 108, 105, 97, 110, 32, 100, 97, 121, 46, 10, 32, 32, 85, 78, 73, 81, 85, 69, 40, 
116, 97, 114, 103, 101, 116, 44, 32, 115, 114, 99, 116, 121, 112, 101, 44, 32, 115, 114, 99, 
105, 100, 41, 10, 41, 59, 10, 67, 82, 69, 65, 84, 69, 32, 73, 78, 68, 69, 88, 32, 
114, 101, 112, 111, 46, 98, 97, 99, 107, 108, 105, 110, 107, 95, 115, 114, 99, 32, 79, 78, 
32, 98, 97, 99, 107, 108, 105, 110, 107, 40, 115, 114, 99, 105, 100, 44, 32, 115, 114, 99, 
116, 121, 112, 101, 41, 59, 10, 10, 45, 45, 32, 69, 97, 99, 104, 32, 97, 116, 116, 97, 
99, 104, 109, 101, 110, 116, 32, 105, 115, 32, 97, 110, 32, 101, 110, 116, 114, 121, 32, 105, 
110, 32, 116, 104, 101, 32, 102, 111, 108, 108, 111, 119, 105, 110, 103, 32, 116, 97, 98, 108, 
101, 46, 32, 32, 79, 110, 108, 121, 10, 45, 45, 32, 116, 104, 101, 32, 109, 111, 115, 116, 
32, 114, 101, 99, 101, 110, 116, 32, 97, 116, 116, 97, 99, 104, 109, 101, 110, 116, 32, 40, 
105, 100, 101, 110, 116, 105, 102, 105, 101, 100, 32, 98, 121, 32, 116, 104, 101, 32, 68, 32, 
99, 97, 114, 100, 41, 32, 105, 115, 32, 115, 97, 118, 101, 100, 46, 10, 45, 45, 10, 67, 
82, 69, 65, 84, 69, 32, 84, 65, 66, 76, 69, 32, 114, 101, 112, 111, 46, 97, 116, 116, 
97, 99, 104, 109, 101, 110, 116, 40, 10, 32, 32, 97, 116, 116, 97, 99, 104, 105, 100, 32, 
73, 78, 84, 69, 71, 69, 82, 32, 80, 82, 73, 77, 65, 82, 89, 32, 75, 69, 89, 44, 
32, 32, 32, 45, 45, 32, 76, 111, 99, 97, 108, 32, 105, 100, 32, 102, 111, 114, 32, 116, 
104, 105, 115, 32, 97, 116, 116, 97, 99, 104, 109, 101, 110, 116, 10, 32, 32, 105, 115, 76, 
97, 116, 101, 115, 116, 32, 66, 79, 79, 76, 69, 65, 78, 32, 68, 69, 70, 65, 85, 76, 
84, 32, 48, 44, 32, 32, 32, 32, 32, 45, 45, 32, 84, 114, 117, 101, 32, 105, 102, 32, 
116, 104, 105, 115, 32, 105, 115, 32, 116, 104, 101, 32, 111, 110, 101, 32, 116, 111, 32, 117, 
115, 101, 10, 32, 32, 109, 116, 105, 109, 101, 32, 84, 73, 77, 69, 83, 84, 65, 77, 80, 
44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 
76, 97, 115, 116, 32, 99, 104, 97, 110, 103, 101, 100, 46, 32, 32, 74, 117, 108, 105, 97, 
110, 32, 100, 97, 121, 46, 10, 32, 32, 115, 114, 99, 32, 84, 69, 88, 84, 44, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 45, 45, 32, 85, 85, 73, 68, 32, 111, 102, 32, 116, 104, 101, 32, 97, 116, 116, 97, 
99, 104, 109, 101, 110, 116, 46, 32, 32, 78, 85, 76, 76, 32, 116, 111, 32, 100, 101, 108, 
101, 116, 101, 10, 32, 32, 116, 97, 114, 103, 101, 116, 32, 84, 69, 88, 84, 44, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 
32, 79, 98, 106, 101, 99, 116, 32, 97, 116, 116, 97, 99, 104, 101, 100, 32, 116, 111, 46, 
32, 87, 105, 107, 105, 110, 97, 109, 101, 32, 111, 114, 32, 84, 107, 116, 32, 85, 85, 73, 
68, 10, 32, 32, 102, 105, 108, 101, 110, 97, 109, 101, 32, 84, 69, 88, 84, 44, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 32, 70, 
105, 108, 101, 110, 97, 109, 101, 32, 102, 111, 114, 32, 116, 104, 101, 32, 97, 116, 116, 97, 
99, 104, 109, 101, 110, 116, 10, 32, 32, 99, 111, 109, 109, 101, 110, 116, 32, 84, 69, 88, 
84, 44, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 
32, 45, 45, 32, 67, 111, 109, 109, 101, 110, 116, 32, 97, 115, 115, 111, 99, 105, 97, 116, 
101, 100, 32, 119, 105, 116, 104, 32, 116, 104, 105, 115, 32, 97, 116, 116, 97, 99, 104, 109, 
101, 110, 116, 10, 32, 32, 117, 115, 101, 114, 32, 84, 69, 88, 84, 32, 32, 32, 32, 32, 
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 
32, 78, 97, 109, 101, 32, 111, 102, 32, 117, 115, 101, 114, 32, 97, 100, 100, 105, 110, 103, 
32, 97, 116, 116, 97, 99, 104, 109, 101, 110, 116, 10, 41, 59, 10, 67, 82, 69, 65, 84, 
69, 32, 73, 78, 68, 69, 88, 32, 114, 101, 112, 111, 46, 97, 116, 116, 97, 99, 104, 109, 
101, 110, 116, 95, 105, 100, 120, 49, 32, 79, 78, 32, 97, 116, 116, 97, 99, 104, 109, 101, 
110, 116, 40, 116, 97, 114, 103, 101, 116, 44, 32, 102, 105, 108, 101, 110, 97, 109, 101, 44, 
32, 109, 116, 105, 109, 101, 41, 59, 10, 67, 82, 69, 65, 84, 69, 32, 73, 78, 68, 69, 
88, 32, 114, 101, 112, 111, 46, 97, 116, 116, 97, 99, 104, 109, 101, 110, 116, 95, 105, 100, 
120, 50, 32, 79, 78, 32, 97, 116, 116, 97, 99, 104, 109, 101, 110, 116, 40, 115, 114, 99, 
41, 59, 10, 10, 45, 45, 32, 70, 111, 114, 32, 116, 114, 97, 99, 107, 105, 110, 103, 32, 
99, 104, 101, 114, 114, 121, 112, 105, 99, 107, 32, 109, 101, 114, 103, 101, 115, 10, 67, 82, 
69, 65, 84, 69, 32, 84, 65, 66, 76, 69, 32, 114, 101, 112, 111, 46, 99, 104, 101, 114, 
114, 121, 112, 105, 99, 107, 40, 10, 32, 32, 112, 97, 114, 101, 110, 116, 105, 100, 32, 73, 
78, 84, 44, 10, 32, 32, 99, 104, 105, 108, 100, 105, 100, 32, 73, 78, 84, 44, 10, 32, 
32, 105, 115, 69, 120, 99, 108, 117, 100, 101, 32, 66, 79, 79, 76, 69, 65, 78, 32, 68, 
69, 70, 65, 85, 76, 84, 32, 102, 97, 108, 115, 101, 44, 10, 32, 32, 80, 82, 73, 77, 
65, 82, 89, 32, 75, 69, 89, 40, 112, 97, 114, 101, 110, 116, 105, 100, 44, 32, 99, 104, 
105, 108, 100, 105, 100, 41, 10, 41, 32, 87, 73, 84, 72, 79, 85, 84, 32, 82, 79, 87, 
73, 68, 59, 10, 67, 82, 69, 65, 84, 69, 32, 73, 78, 68, 69, 88, 32, 114, 101, 112, 
111, 46, 99, 104, 101, 114, 114, 121, 112, 105, 99, 107, 95, 99, 105, 100, 32, 79, 78, 32, 
99, 104, 101, 114, 114, 121, 112, 105, 99, 107, 40, 99, 104, 105, 108, 100, 105, 100, 41, 59, 
10, 
0};
char const * fsl_schema_repo2_cstr = fsl_schema_repo2_cstr_a;
/* end of ../sql/repo-transient.sql */
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/schema_ticket_cstr.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/* Binary form of file ../sql/ticket.sql */
/** @page page_schema_ticket_cstr Schema: ticket.sql
@code
-- Template for the TICKET table
CREATE TABLE repo.ticket(
  -- Do not change any column that begins with tkt_
  tkt_id INTEGER PRIMARY KEY,
  tkt_uuid TEXT UNIQUE,
  tkt_mtime DATE,
  tkt_ctime DATE,
  -- Add as many field as required below this line
  type TEXT,
  status TEXT,
  subsystem TEXT,
  priority TEXT,
  severity TEXT,
  foundin TEXT,
  private_contact TEXT,
  resolution TEXT,
  title TEXT,
  comment TEXT
);
CREATE TABLE repo.ticketchng(
  -- Do not change any column that begins with tkt_
  tkt_id INTEGER REFERENCES ticket,
  tkt_rid INTEGER REFERENCES blob,
  tkt_mtime DATE,
  -- Add as many fields as required below this line
  login TEXT,
  username TEXT,
  mimetype TEXT,
  icomment TEXT
);
CREATE INDEX repo.ticketchng_idx1 ON ticketchng(tkt_id, tkt_mtime);
 @endcode
 @see schema_ticket()
*/
/* auto-generated code - edit at your own risk! (Good luck with that!) */
static char const fsl_schema_ticket_cstr_a[] = {
45, 45, 32, 84, 101, 109, 112, 108, 97, 116, 101, 32, 102, 111, 114, 32, 116, 104, 101, 32, 
84, 73, 67, 75, 69, 84, 32, 116, 97, 98, 108, 101, 10, 67, 82, 69, 65, 84, 69, 32, 
84, 65, 66, 76, 69, 32, 114, 101, 112, 111, 46, 116, 105, 99, 107, 101, 116, 40, 10, 32, 
32, 45, 45, 32, 68, 111, 32, 110, 111, 116, 32, 99, 104, 97, 110, 103, 101, 32, 97, 110, 
121, 32, 99, 111, 108, 117, 109, 110, 32, 116, 104, 97, 116, 32, 98, 101, 103, 105, 110, 115, 
32, 119, 105, 116, 104, 32, 116, 107, 116, 95, 10, 32, 32, 116, 107, 116, 95, 105, 100, 32, 
73, 78, 84, 69, 71, 69, 82, 32, 80, 82, 73, 77, 65, 82, 89, 32, 75, 69, 89, 44, 
10, 32, 32, 116, 107, 116, 95, 117, 117, 105, 100, 32, 84, 69, 88, 84, 32, 85, 78, 73, 
81, 85, 69, 44, 10, 32, 32, 116, 107, 116, 95, 109, 116, 105, 109, 101, 32, 68, 65, 84, 
69, 44, 10, 32, 32, 116, 107, 116, 95, 99, 116, 105, 109, 101, 32, 68, 65, 84, 69, 44, 
10, 32, 32, 45, 45, 32, 65, 100, 100, 32, 97, 115, 32, 109, 97, 110, 121, 32, 102, 105, 
101, 108, 100, 32, 97, 115, 32, 114, 101, 113, 117, 105, 114, 101, 100, 32, 98, 101, 108, 111, 
119, 32, 116, 104, 105, 115, 32, 108, 105, 110, 101, 10, 32, 32, 116, 121, 112, 101, 32, 84, 
69, 88, 84, 44, 10, 32, 32, 115, 116, 97, 116, 117, 115, 32, 84, 69, 88, 84, 44, 10, 
32, 32, 115, 117, 98, 115, 121, 115, 116, 101, 109, 32, 84, 69, 88, 84, 44, 10, 32, 32, 
112, 114, 105, 111, 114, 105, 116, 121, 32, 84, 69, 88, 84, 44, 10, 32, 32, 115, 101, 118, 
101, 114, 105, 116, 121, 32, 84, 69, 88, 84, 44, 10, 32, 32, 102, 111, 117, 110, 100, 105, 
110, 32, 84, 69, 88, 84, 44, 10, 32, 32, 112, 114, 105, 118, 97, 116, 101, 95, 99, 111, 
110, 116, 97, 99, 116, 32, 84, 69, 88, 84, 44, 10, 32, 32, 114, 101, 115, 111, 108, 117, 
116, 105, 111, 110, 32, 84, 69, 88, 84, 44, 10, 32, 32, 116, 105, 116, 108, 101, 32, 84, 
69, 88, 84, 44, 10, 32, 32, 99, 111, 109, 109, 101, 110, 116, 32, 84, 69, 88, 84, 10, 
41, 59, 10, 67, 82, 69, 65, 84, 69, 32, 84, 65, 66, 76, 69, 32, 114, 101, 112, 111, 
46, 116, 105, 99, 107, 101, 116, 99, 104, 110, 103, 40, 10, 32, 32, 45, 45, 32, 68, 111, 
32, 110, 111, 116, 32, 99, 104, 97, 110, 103, 101, 32, 97, 110, 121, 32, 99, 111, 108, 117, 
109, 110, 32, 116, 104, 97, 116, 32, 98, 101, 103, 105, 110, 115, 32, 119, 105, 116, 104, 32, 
116, 107, 116, 95, 10, 32, 32, 116, 107, 116, 95, 105, 100, 32, 73, 78, 84, 69, 71, 69, 
82, 32, 82, 69, 70, 69, 82, 69, 78, 67, 69, 83, 32, 116, 105, 99, 107, 101, 116, 44, 
10, 32, 32, 116, 107, 116, 95, 114, 105, 100, 32, 73, 78, 84, 69, 71, 69, 82, 32, 82, 
69, 70, 69, 82, 69, 78, 67, 69, 83, 32, 98, 108, 111, 98, 44, 10, 32, 32, 116, 107, 
116, 95, 109, 116, 105, 109, 101, 32, 68, 65, 84, 69, 44, 10, 32, 32, 45, 45, 32, 65, 
100, 100, 32, 97, 115, 32, 109, 97, 110, 121, 32, 102, 105, 101, 108, 100, 115, 32, 97, 115, 
32, 114, 101, 113, 117, 105, 114, 101, 100, 32, 98, 101, 108, 111, 119, 32, 116, 104, 105, 115, 
32, 108, 105, 110, 101, 10, 32, 32, 108, 111, 103, 105, 110, 32, 84, 69, 88, 84, 44, 10, 
32, 32, 117, 115, 101, 114, 110, 97, 109, 101, 32, 84, 69, 88, 84, 44, 10, 32, 32, 109, 
105, 109, 101, 116, 121, 112, 101, 32, 84, 69, 88, 84, 44, 10, 32, 32, 105, 99, 111, 109, 
109, 101, 110, 116, 32, 84, 69, 88, 84, 10, 41, 59, 10, 67, 82, 69, 65, 84, 69, 32, 
73, 78, 68, 69, 88, 32, 114, 101, 112, 111, 46, 116, 105, 99, 107, 101, 116, 99, 104, 110, 
103, 95, 105, 100, 120, 49, 32, 79, 78, 32, 116, 105, 99, 107, 101, 116, 99, 104, 110, 103, 
40, 116, 107, 116, 95, 105, 100, 44, 32, 116, 107, 116, 95, 109, 116, 105, 109, 101, 41, 59, 
10, 
0};
char const * fsl_schema_ticket_cstr = fsl_schema_ticket_cstr_a;
/* end of ../sql/ticket.sql */
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




































































































































































Deleted src/schema_ticket_reports_cstr.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/* Binary form of file ../sql/ticket-reports.sql */
/** @page page_schema_ticket_reports_cstr Schema: ticket-reports.sql
@code
INSERT INTO reportfmt(title,mtime,cols,sqlcode) 
VALUES('All Tickets',julianday('1970-01-01'),'#ffffff Key:
#f2dcdc Active
#e8e8e8 Review
#cfe8bd Fixed
#bde5d6 Tested
#cacae5 Deferred
#c8c8c8 Closed','SELECT
  CASE WHEN status IN (''Open'',''Verified'') THEN ''#f2dcdc''
       WHEN status=''Review'' THEN ''#e8e8e8''
       WHEN status=''Fixed'' THEN ''#cfe8bd''
       WHEN status=''Tested'' THEN ''#bde5d6''
       WHEN status=''Deferred'' THEN ''#cacae5''
       ELSE ''#c8c8c8'' END AS ''bgcolor'',
  substr(tkt_uuid,1,10) AS ''#'',
  datetime(tkt_mtime) AS ''mtime'',
  type,
  status,
  subsystem,
  title
FROM ticket');
 @endcode
 @see schema_ticket_reports()
*/
/* auto-generated code - edit at your own risk! (Good luck with that!) */
static char const fsl_schema_ticket_reports_cstr_a[] = {
73, 78, 83, 69, 82, 84, 32, 73, 78, 84, 79, 32, 114, 101, 112, 111, 114, 116, 102, 109, 
116, 40, 116, 105, 116, 108, 101, 44, 109, 116, 105, 109, 101, 44, 99, 111, 108, 115, 44, 115, 
113, 108, 99, 111, 100, 101, 41, 32, 10, 86, 65, 76, 85, 69, 83, 40, 39, 65, 108, 108, 
32, 84, 105, 99, 107, 101, 116, 115, 39, 44, 106, 117, 108, 105, 97, 110, 100, 97, 121, 40, 
39, 49, 57, 55, 48, 45, 48, 49, 45, 48, 49, 39, 41, 44, 39, 35, 102, 102, 102, 102, 
102, 102, 32, 75, 101, 121, 58, 10, 35, 102, 50, 100, 99, 100, 99, 32, 65, 99, 116, 105, 
118, 101, 10, 35, 101, 56, 101, 56, 101, 56, 32, 82, 101, 118, 105, 101, 119, 10, 35, 99, 
102, 101, 56, 98, 100, 32, 70, 105, 120, 101, 100, 10, 35, 98, 100, 101, 53, 100, 54, 32, 
84, 101, 115, 116, 101, 100, 10, 35, 99, 97, 99, 97, 101, 53, 32, 68, 101, 102, 101, 114, 
114, 101, 100, 10, 35, 99, 56, 99, 56, 99, 56, 32, 67, 108, 111, 115, 101, 100, 39, 44, 
39, 83, 69, 76, 69, 67, 84, 10, 32, 32, 67, 65, 83, 69, 32, 87, 72, 69, 78, 32, 
115, 116, 97, 116, 117, 115, 32, 73, 78, 32, 40, 39, 39, 79, 112, 101, 110, 39, 39, 44, 
39, 39, 86, 101, 114, 105, 102, 105, 101, 100, 39, 39, 41, 32, 84, 72, 69, 78, 32, 39, 
39, 35, 102, 50, 100, 99, 100, 99, 39, 39, 10, 32, 32, 32, 32, 32, 32, 32, 87, 72, 
69, 78, 32, 115, 116, 97, 116, 117, 115, 61, 39, 39, 82, 101, 118, 105, 101, 119, 39, 39, 
32, 84, 72, 69, 78, 32, 39, 39, 35, 101, 56, 101, 56, 101, 56, 39, 39, 10, 32, 32, 
32, 32, 32, 32, 32, 87, 72, 69, 78, 32, 115, 116, 97, 116, 117, 115, 61, 39, 39, 70, 
105, 120, 101, 100, 39, 39, 32, 84, 72, 69, 78, 32, 39, 39, 35, 99, 102, 101, 56, 98, 
100, 39, 39, 10, 32, 32, 32, 32, 32, 32, 32, 87, 72, 69, 78, 32, 115, 116, 97, 116, 
117, 115, 61, 39, 39, 84, 101, 115, 116, 101, 100, 39, 39, 32, 84, 72, 69, 78, 32, 39, 
39, 35, 98, 100, 101, 53, 100, 54, 39, 39, 10, 32, 32, 32, 32, 32, 32, 32, 87, 72, 
69, 78, 32, 115, 116, 97, 116, 117, 115, 61, 39, 39, 68, 101, 102, 101, 114, 114, 101, 100, 
39, 39, 32, 84, 72, 69, 78, 32, 39, 39, 35, 99, 97, 99, 97, 101, 53, 39, 39, 10, 
32, 32, 32, 32, 32, 32, 32, 69, 76, 83, 69, 32, 39, 39, 35, 99, 56, 99, 56, 99, 
56, 39, 39, 32, 69, 78, 68, 32, 65, 83, 32, 39, 39, 98, 103, 99, 111, 108, 111, 114, 
39, 39, 44, 10, 32, 32, 115, 117, 98, 115, 116, 114, 40, 116, 107, 116, 95, 117, 117, 105, 
100, 44, 49, 44, 49, 48, 41, 32, 65, 83, 32, 39, 39, 35, 39, 39, 44, 10, 32, 32, 
100, 97, 116, 101, 116, 105, 109, 101, 40, 116, 107, 116, 95, 109, 116, 105, 109, 101, 41, 32, 
65, 83, 32, 39, 39, 109, 116, 105, 109, 101, 39, 39, 44, 10, 32, 32, 116, 121, 112, 101, 
44, 10, 32, 32, 115, 116, 97, 116, 117, 115, 44, 10, 32, 32, 115, 117, 98, 115, 121, 115, 
116, 101, 109, 44, 10, 32, 32, 116, 105, 116, 108, 101, 10, 70, 82, 79, 77, 32, 116, 105, 
99, 107, 101, 116, 39, 41, 59, 10, 
0};
char const * fsl_schema_ticket_reports_cstr = fsl_schema_ticket_reports_cstr_a;
/* end of ../sql/ticket-reports.sql */
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
































































































































Deleted src/search.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 houses some FTS-search-related functionality.

  libfossil does not aim to reproduce all search functionality
  provided by fossil. Initially, at least, the only planned feature
  parity is that of updating the search index as content is
  added/updated.
*/
#include "fossil-scm/fossil-internal.h"
#include "fossil-scm/fossil-confdb.h"
#include <assert.h>
#include <memory.h> /* memcmp() */

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

static bool fsl_search_ndx_exists(fsl_cx * const f){
  if(f->cache.searchIndexExists<0){
    f->cache.searchIndexExists = fsl_db_table_exists(fsl_cx_db_repo(f),
                                                     FSL_DBROLE_REPO,
                                                     "ftsdocs")
      ? 1 : 0;
  }
  return f->cache.searchIndexExists ? true : false;
}

static char fsl_satype_letter(fsl_satype_e t){
  switch(t){
    case FSL_SATYPE_CHECKIN: return 'c';
    case FSL_SATYPE_WIKI: return 'w';
    case FSL_SATYPE_TICKET: return 't';
    case FSL_SATYPE_FORUMPOST: return 'f';
    case FSL_SATYPE_TECHNOTE: return 'e';
    default:
      assert(!"Internal misuse of fsl_satype_letter()");
      return 0;
  }
}

int fsl_search_doc_touch(fsl_cx *f, fsl_satype_e saType, fsl_id_t rid,
                         const char * docName){
  if(!fsl_search_ndx_exists(f) || fsl_content_is_private(f, rid)) return 0;
  char zType[2] = {0,0};
  zType[0] = fsl_satype_letter(saType);
#if 0
  /* See MARKER() call in the #else block */
  assert(*zType);
  return *zType ? 0 : FSL_RC_MISUSE;
#else  
  /* Reminder: fossil(1) does some once-per-connection init here which
     installs UDFs used by the search process. Those will be significant
     for us if we add the search features to the library. */
  assert(zType[0] && "Misuse of fsl_search_doc_touch()'s 2nd parameter.");
  fsl_db * const db = fsl_cx_db_repo(f);
  int rc = fsl_db_exec(db,
       "DELETE FROM ftsidx WHERE docid IN"
       "    (SELECT rowid FROM ftsdocs WHERE type=%Q AND rid=%"FSL_ID_T_PFMT" AND idxed)",
       zType, rid );
  if(rc){
    // For reasons i don't understand, this query fails with "SQL logic error"
    // when run from here, but succeeds fine in fossil and fossil's SQL shell.
    /*MARKER(("type=%s rid=%d rc=%s\n",zType, (int)rid, fsl_rc_cstr(rc)));*/
    goto end;
  }
  rc = fsl_db_exec(db,
       "REPLACE INTO ftsdocs(type,rid,name,idxed)"
       " VALUES(%Q,%"FSL_ID_T_PFMT",%Q,0)",
       zType, rid, docName );
  if(rc) goto end;
  if( FSL_SATYPE_WIKI==saType || FSL_SATYPE_TECHNOTE==saType ){
    rc = fsl_db_exec(db,
        "DELETE FROM ftsidx WHERE docid IN"
        "    (SELECT rowid FROM ftsdocs WHERE type=%Q AND name=%Q AND idxed)",
        zType, docName );
    if(!rc) rc = fsl_db_exec(db,
        "DELETE FROM ftsdocs WHERE type=%Q AND name=%Q AND rid!=%"FSL_ID_T_PFMT,
        zType, docName, rid );
  }
  /* All forum posts are always indexed */
  end:
  return rc;
#endif
}

#undef MARKER
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










































































































































































































Deleted src/sha1.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.h"
#include <assert.h>
#include <string.h> /* strlen() */
#include <stddef.h> /* NULL on linux */

#include <sys/types.h>

#if FSL_SHA1_HARDENED
/*************** File:  lib/sha1.c ****************/
/***
* Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow (danshu@microsoft.com)
* Distributed under the MIT Software License.
* See accompanying file LICENSE.txt or copy at
* https://opensource.org/licenses/MIT
***/
/*************** File:  LICENSE.txt ***************/
/*
** MIT License
**
** Copyright (c) 2017:
**     Marc Stevens
**     Cryptology Group
**     Centrum Wiskunde & Informatica
**     P.O. Box 94079, 1090 GB Amsterdam, Netherlands
**     marc@marc-stevens.nl
**
**     Dan Shumow
**     Microsoft Research
**     danshu@microsoft.com
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the Software is
** furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in all
** copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
** SOFTWARE.
*/

#include <string.h>
#include <memory.h>

#define DVMASKSIZE 1
typedef struct { int dvType; int dvK; int dvB; int testt; int maski; int maskb; uint32_t dm[80]; } dv_info_t;
#define DOSTORESTATE58
#define DOSTORESTATE65
typedef void(*sha1_recompression_type)(uint32_t*, uint32_t*, const uint32_t*, const uint32_t*);
static void sha1_message_expansion(uint32_t W[80]);
static void sha1_compression_W(uint32_t ihv[5], const uint32_t W[80]);
static void sha1_compression_states(uint32_t ihv[5], const uint32_t W[80], uint32_t states[80][5]);

/******************** File: lib/ubc_check.c **************************/
/***
* Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow <danshu@microsoft.com>
* Distributed under the MIT Software License.
* See accompanying file LICENSE.txt or copy at
* https://opensource.org/licenses/MIT
***/

/*
** this file was generated by the 'parse_bitrel' program in the tools section
** using the data files from directory 'tools/data/3565'
**
** sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check
** dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper)
** dm[80] is the expanded message block XOR-difference defined by the DV
** testt is the step to do the recompression from for collision detection
** maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check
**
** ubc_check takes as input an expanded message block and verifies the unavoidable bitconditions for all listed DVs
** it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions for that DV have been met
** thus one needs to do the recompression check for each DV that has its bit set
**
** ubc_check is programmatically generated and the unavoidable bitconditions have been hardcoded
** a directly verifiable version named ubc_check_verify can be found in ubc_check_verify.c
** ubc_check has been verified against ubc_check_verify using the 'ubc_check_test' program in the tools section
*/

static const uint32_t DV_I_43_0_bit   = (uint32_t)(1) << 0;
static const uint32_t DV_I_44_0_bit   = (uint32_t)(1) << 1;
static const uint32_t DV_I_45_0_bit   = (uint32_t)(1) << 2;
static const uint32_t DV_I_46_0_bit   = (uint32_t)(1) << 3;
static const uint32_t DV_I_46_2_bit   = (uint32_t)(1) << 4;
static const uint32_t DV_I_47_0_bit   = (uint32_t)(1) << 5;
static const uint32_t DV_I_47_2_bit   = (uint32_t)(1) << 6;
static const uint32_t DV_I_48_0_bit   = (uint32_t)(1) << 7;
static const uint32_t DV_I_48_2_bit   = (uint32_t)(1) << 8;
static const uint32_t DV_I_49_0_bit   = (uint32_t)(1) << 9;
static const uint32_t DV_I_49_2_bit   = (uint32_t)(1) << 10;
static const uint32_t DV_I_50_0_bit   = (uint32_t)(1) << 11;
static const uint32_t DV_I_50_2_bit   = (uint32_t)(1) << 12;
static const uint32_t DV_I_51_0_bit   = (uint32_t)(1) << 13;
static const uint32_t DV_I_51_2_bit   = (uint32_t)(1) << 14;
static const uint32_t DV_I_52_0_bit   = (uint32_t)(1) << 15;
static const uint32_t DV_II_45_0_bit   = (uint32_t)(1) << 16;
static const uint32_t DV_II_46_0_bit   = (uint32_t)(1) << 17;
static const uint32_t DV_II_46_2_bit   = (uint32_t)(1) << 18;
static const uint32_t DV_II_47_0_bit   = (uint32_t)(1) << 19;
static const uint32_t DV_II_48_0_bit   = (uint32_t)(1) << 20;
static const uint32_t DV_II_49_0_bit   = (uint32_t)(1) << 21;
static const uint32_t DV_II_49_2_bit   = (uint32_t)(1) << 22;
static const uint32_t DV_II_50_0_bit   = (uint32_t)(1) << 23;
static const uint32_t DV_II_50_2_bit   = (uint32_t)(1) << 24;
static const uint32_t DV_II_51_0_bit   = (uint32_t)(1) << 25;
static const uint32_t DV_II_51_2_bit   = (uint32_t)(1) << 26;
static const uint32_t DV_II_52_0_bit   = (uint32_t)(1) << 27;
static const uint32_t DV_II_53_0_bit   = (uint32_t)(1) << 28;
static const uint32_t DV_II_54_0_bit   = (uint32_t)(1) << 29;
static const uint32_t DV_II_55_0_bit   = (uint32_t)(1) << 30;
static const uint32_t DV_II_56_0_bit   = (uint32_t)(1) << 31;

dv_info_t sha1_dvs[] =
{
  {1,43,0,58,0,0, { 0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803,0x80000161,0x80000599 } }
, {1,44,0,58,0,1, { 0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803,0x80000161 } }
, {1,45,0,58,0,2, { 0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803 } }
, {1,46,0,58,0,3, { 0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c } }
, {1,46,2,58,0,4, { 0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020,0x0000039a,0x00000132 } }
, {1,47,0,58,0,5, { 0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6 } }
, {1,47,2,58,0,6, { 0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020,0x0000039a } }
, {1,48,0,58,0,7, { 0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408 } }
, {1,48,2,58,0,8, { 0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020 } }
, {1,49,0,58,0,9, { 0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164 } }
, {1,49,2,58,0,10, { 0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590 } }
, {1,50,0,65,0,11, { 0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018 } }
, {1,50,2,65,0,12, { 0x20000030,0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060 } }
, {1,51,0,65,0,13, { 0xe8000000,0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202 } }
, {1,51,2,65,0,14, { 0xa0000003,0x20000030,0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a } }
, {1,52,0,65,0,15, { 0x04000010,0xe8000000,0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012 } }
, {2,45,0,58,0,16, { 0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4,0x80000054,0x00000967 } }
, {2,46,0,58,0,17, { 0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4,0x80000054 } }
, {2,46,2,58,0,18, { 0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c,0x000005b6,0x0000106a,0x00000b90,0x00000152 } }
, {2,47,0,58,0,19, { 0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4 } }
, {2,48,0,58,0,20, { 0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a } }
, {2,49,0,58,0,21, { 0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d } }
, {2,49,2,58,0,22, { 0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c,0x000005b6 } }
, {2,50,0,65,0,23, { 0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b } }
, {2,50,2,65,0,24, { 0xd0000072,0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c } }
, {2,51,0,65,0,25, { 0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b } }
, {2,51,2,65,0,26, { 0x00000043,0xd0000072,0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e } }
, {2,52,0,65,0,27, { 0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014 } }
, {2,53,0,65,0,28, { 0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089 } }
, {2,54,0,65,0,29, { 0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107 } }
, {2,55,0,65,0,30, { 0x00000010,0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b } }
, {2,56,0,65,0,31, { 0x2600001a,0x00000010,0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046 } }
, {0,0,0,0,0,0, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}
};
void ubc_check(const uint32_t W[80], uint32_t dvmask[1])
{
  uint32_t mask = ~((uint32_t)(0));
  mask &= (((((W[44]^W[45])>>29)&1)-1) | ~(DV_I_48_0_bit|DV_I_51_0_bit|DV_I_52_0_bit|DV_II_45_0_bit|DV_II_46_0_bit|DV_II_50_0_bit|DV_II_51_0_bit));
  mask &= (((((W[49]^W[50])>>29)&1)-1) | ~(DV_I_46_0_bit|DV_II_45_0_bit|DV_II_50_0_bit|DV_II_51_0_bit|DV_II_55_0_bit|DV_II_56_0_bit));
  mask &= (((((W[48]^W[49])>>29)&1)-1) | ~(DV_I_45_0_bit|DV_I_52_0_bit|DV_II_49_0_bit|DV_II_50_0_bit|DV_II_54_0_bit|DV_II_55_0_bit));
  mask &= ((((W[47]^(W[50]>>25))&(1<<4))-(1<<4)) | ~(DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_51_0_bit|DV_II_56_0_bit));
  mask &= (((((W[47]^W[48])>>29)&1)-1) | ~(DV_I_44_0_bit|DV_I_51_0_bit|DV_II_48_0_bit|DV_II_49_0_bit|DV_II_53_0_bit|DV_II_54_0_bit));
  mask &= (((((W[46]>>4)^(W[49]>>29))&1)-1) | ~(DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit|DV_II_50_0_bit|DV_II_55_0_bit));
  mask &= (((((W[46]^W[47])>>29)&1)-1) | ~(DV_I_43_0_bit|DV_I_50_0_bit|DV_II_47_0_bit|DV_II_48_0_bit|DV_II_52_0_bit|DV_II_53_0_bit));
  mask &= (((((W[45]>>4)^(W[48]>>29))&1)-1) | ~(DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit|DV_II_49_0_bit|DV_II_54_0_bit));
  mask &= (((((W[45]^W[46])>>29)&1)-1) | ~(DV_I_49_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_47_0_bit|DV_II_51_0_bit|DV_II_52_0_bit));
  mask &= (((((W[44]>>4)^(W[47]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit|DV_II_48_0_bit|DV_II_53_0_bit));
  mask &= (((((W[43]>>4)^(W[46]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit|DV_II_47_0_bit|DV_II_52_0_bit));
  mask &= (((((W[43]^W[44])>>29)&1)-1) | ~(DV_I_47_0_bit|DV_I_50_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_49_0_bit|DV_II_50_0_bit));
  mask &= (((((W[42]>>4)^(W[45]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_51_0_bit));
  mask &= (((((W[41]>>4)^(W[44]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_50_0_bit));
  mask &= (((((W[40]^W[41])>>29)&1)-1) | ~(DV_I_44_0_bit|DV_I_47_0_bit|DV_I_48_0_bit|DV_II_46_0_bit|DV_II_47_0_bit|DV_II_56_0_bit));
  mask &= (((((W[54]^W[55])>>29)&1)-1) | ~(DV_I_51_0_bit|DV_II_47_0_bit|DV_II_50_0_bit|DV_II_55_0_bit|DV_II_56_0_bit));
  mask &= (((((W[53]^W[54])>>29)&1)-1) | ~(DV_I_50_0_bit|DV_II_46_0_bit|DV_II_49_0_bit|DV_II_54_0_bit|DV_II_55_0_bit));
  mask &= (((((W[52]^W[53])>>29)&1)-1) | ~(DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit|DV_II_53_0_bit|DV_II_54_0_bit));
  mask &= ((((W[50]^(W[53]>>25))&(1<<4))-(1<<4)) | ~(DV_I_50_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_48_0_bit|DV_II_54_0_bit));
  mask &= (((((W[50]^W[51])>>29)&1)-1) | ~(DV_I_47_0_bit|DV_II_46_0_bit|DV_II_51_0_bit|DV_II_52_0_bit|DV_II_56_0_bit));
  mask &= ((((W[49]^(W[52]>>25))&(1<<4))-(1<<4)) | ~(DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_47_0_bit|DV_II_53_0_bit));
  mask &= ((((W[48]^(W[51]>>25))&(1<<4))-(1<<4)) | ~(DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_52_0_bit));
  mask &= (((((W[42]^W[43])>>29)&1)-1) | ~(DV_I_46_0_bit|DV_I_49_0_bit|DV_I_50_0_bit|DV_II_48_0_bit|DV_II_49_0_bit));
  mask &= (((((W[41]^W[42])>>29)&1)-1) | ~(DV_I_45_0_bit|DV_I_48_0_bit|DV_I_49_0_bit|DV_II_47_0_bit|DV_II_48_0_bit));
  mask &= (((((W[40]>>4)^(W[43]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_50_0_bit|DV_II_49_0_bit|DV_II_56_0_bit));
  mask &= (((((W[39]>>4)^(W[42]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_49_0_bit|DV_II_48_0_bit|DV_II_55_0_bit));
  if (mask & (DV_I_44_0_bit|DV_I_48_0_bit|DV_II_47_0_bit|DV_II_54_0_bit|DV_II_56_0_bit))
    mask &= (((((W[38]>>4)^(W[41]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_48_0_bit|DV_II_47_0_bit|DV_II_54_0_bit|DV_II_56_0_bit));
  mask &= (((((W[37]>>4)^(W[40]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_47_0_bit|DV_II_46_0_bit|DV_II_53_0_bit|DV_II_55_0_bit));
  if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_51_0_bit|DV_II_56_0_bit))
    mask &= (((((W[55]^W[56])>>29)&1)-1) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_51_0_bit|DV_II_56_0_bit));
  if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_50_0_bit|DV_II_56_0_bit))
    mask &= ((((W[52]^(W[55]>>25))&(1<<4))-(1<<4)) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_50_0_bit|DV_II_56_0_bit));
  if (mask & (DV_I_51_0_bit|DV_II_47_0_bit|DV_II_49_0_bit|DV_II_55_0_bit))
    mask &= ((((W[51]^(W[54]>>25))&(1<<4))-(1<<4)) | ~(DV_I_51_0_bit|DV_II_47_0_bit|DV_II_49_0_bit|DV_II_55_0_bit));
  if (mask & (DV_I_48_0_bit|DV_II_47_0_bit|DV_II_52_0_bit|DV_II_53_0_bit))
    mask &= (((((W[51]^W[52])>>29)&1)-1) | ~(DV_I_48_0_bit|DV_II_47_0_bit|DV_II_52_0_bit|DV_II_53_0_bit));
  if (mask & (DV_I_46_0_bit|DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit))
    mask &= (((((W[36]>>4)^(W[40]>>29))&1)-1) | ~(DV_I_46_0_bit|DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit));
  if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_49_0_bit))
    mask &= ((0-(((W[53]^W[56])>>29)&1)) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_49_0_bit));
  if (mask & (DV_I_50_0_bit|DV_II_46_0_bit|DV_II_47_0_bit))
    mask &= ((0-(((W[51]^W[54])>>29)&1)) | ~(DV_I_50_0_bit|DV_II_46_0_bit|DV_II_47_0_bit));
  if (mask & (DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit))
    mask &= ((0-(((W[50]^W[52])>>29)&1)) | ~(DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit));
  if (mask & (DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit))
    mask &= ((0-(((W[49]^W[51])>>29)&1)) | ~(DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit));
  if (mask & (DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit))
    mask &= ((0-(((W[48]^W[50])>>29)&1)) | ~(DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit));
  if (mask & (DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit))
    mask &= ((0-(((W[47]^W[49])>>29)&1)) | ~(DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit));
  if (mask & (DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit))
    mask &= ((0-(((W[46]^W[48])>>29)&1)) | ~(DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit));
  mask &= ((((W[45]^W[47])&(1<<6))-(1<<6)) | ~(DV_I_47_2_bit|DV_I_49_2_bit|DV_I_51_2_bit));
  if (mask & (DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit))
    mask &= ((0-(((W[45]^W[47])>>29)&1)) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit));
  mask &= (((((W[44]^W[46])>>6)&1)-1) | ~(DV_I_46_2_bit|DV_I_48_2_bit|DV_I_50_2_bit));
  if (mask & (DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit))
    mask &= ((0-(((W[44]^W[46])>>29)&1)) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit));
  mask &= ((0-((W[41]^(W[42]>>5))&(1<<1))) | ~(DV_I_48_2_bit|DV_II_46_2_bit|DV_II_51_2_bit));
  mask &= ((0-((W[40]^(W[41]>>5))&(1<<1))) | ~(DV_I_47_2_bit|DV_I_51_2_bit|DV_II_50_2_bit));
  if (mask & (DV_I_44_0_bit|DV_I_46_0_bit|DV_II_56_0_bit))
    mask &= ((0-(((W[40]^W[42])>>4)&1)) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_II_56_0_bit));
  mask &= ((0-((W[39]^(W[40]>>5))&(1<<1))) | ~(DV_I_46_2_bit|DV_I_50_2_bit|DV_II_49_2_bit));
  if (mask & (DV_I_43_0_bit|DV_I_45_0_bit|DV_II_55_0_bit))
    mask &= ((0-(((W[39]^W[41])>>4)&1)) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_II_55_0_bit));
  if (mask & (DV_I_44_0_bit|DV_II_54_0_bit|DV_II_56_0_bit))
    mask &= ((0-(((W[38]^W[40])>>4)&1)) | ~(DV_I_44_0_bit|DV_II_54_0_bit|DV_II_56_0_bit));
  if (mask & (DV_I_43_0_bit|DV_II_53_0_bit|DV_II_55_0_bit))
    mask &= ((0-(((W[37]^W[39])>>4)&1)) | ~(DV_I_43_0_bit|DV_II_53_0_bit|DV_II_55_0_bit));
  mask &= ((0-((W[36]^(W[37]>>5))&(1<<1))) | ~(DV_I_47_2_bit|DV_I_50_2_bit|DV_II_46_2_bit));
  if (mask & (DV_I_45_0_bit|DV_I_48_0_bit|DV_II_47_0_bit))
    mask &= (((((W[35]>>4)^(W[39]>>29))&1)-1) | ~(DV_I_45_0_bit|DV_I_48_0_bit|DV_II_47_0_bit));
  if (mask & (DV_I_48_0_bit|DV_II_48_0_bit))
    mask &= ((0-((W[63]^(W[64]>>5))&(1<<0))) | ~(DV_I_48_0_bit|DV_II_48_0_bit));
  if (mask & (DV_I_45_0_bit|DV_II_45_0_bit))
    mask &= ((0-((W[63]^(W[64]>>5))&(1<<1))) | ~(DV_I_45_0_bit|DV_II_45_0_bit));
  if (mask & (DV_I_47_0_bit|DV_II_47_0_bit))
    mask &= ((0-((W[62]^(W[63]>>5))&(1<<0))) | ~(DV_I_47_0_bit|DV_II_47_0_bit));
  if (mask & (DV_I_46_0_bit|DV_II_46_0_bit))
    mask &= ((0-((W[61]^(W[62]>>5))&(1<<0))) | ~(DV_I_46_0_bit|DV_II_46_0_bit));
  mask &= ((0-((W[61]^(W[62]>>5))&(1<<2))) | ~(DV_I_46_2_bit|DV_II_46_2_bit));
  if (mask & (DV_I_45_0_bit|DV_II_45_0_bit))
    mask &= ((0-((W[60]^(W[61]>>5))&(1<<0))) | ~(DV_I_45_0_bit|DV_II_45_0_bit));
  if (mask & (DV_II_51_0_bit|DV_II_54_0_bit))
    mask &= (((((W[58]^W[59])>>29)&1)-1) | ~(DV_II_51_0_bit|DV_II_54_0_bit));
  if (mask & (DV_II_50_0_bit|DV_II_53_0_bit))
    mask &= (((((W[57]^W[58])>>29)&1)-1) | ~(DV_II_50_0_bit|DV_II_53_0_bit));
  if (mask & (DV_II_52_0_bit|DV_II_54_0_bit))
    mask &= ((((W[56]^(W[59]>>25))&(1<<4))-(1<<4)) | ~(DV_II_52_0_bit|DV_II_54_0_bit));
  if (mask & (DV_II_51_0_bit|DV_II_52_0_bit))
    mask &= ((0-(((W[56]^W[59])>>29)&1)) | ~(DV_II_51_0_bit|DV_II_52_0_bit));
  if (mask & (DV_II_49_0_bit|DV_II_52_0_bit))
    mask &= (((((W[56]^W[57])>>29)&1)-1) | ~(DV_II_49_0_bit|DV_II_52_0_bit));
  if (mask & (DV_II_51_0_bit|DV_II_53_0_bit))
    mask &= ((((W[55]^(W[58]>>25))&(1<<4))-(1<<4)) | ~(DV_II_51_0_bit|DV_II_53_0_bit));
  if (mask & (DV_II_50_0_bit|DV_II_52_0_bit))
    mask &= ((((W[54]^(W[57]>>25))&(1<<4))-(1<<4)) | ~(DV_II_50_0_bit|DV_II_52_0_bit));
  if (mask & (DV_II_49_0_bit|DV_II_51_0_bit))
    mask &= ((((W[53]^(W[56]>>25))&(1<<4))-(1<<4)) | ~(DV_II_49_0_bit|DV_II_51_0_bit));
  mask &= ((((W[51]^(W[50]>>5))&(1<<1))-(1<<1)) | ~(DV_I_50_2_bit|DV_II_46_2_bit));
  mask &= ((((W[48]^W[50])&(1<<6))-(1<<6)) | ~(DV_I_50_2_bit|DV_II_46_2_bit));
  if (mask & (DV_I_51_0_bit|DV_I_52_0_bit))
    mask &= ((0-(((W[48]^W[55])>>29)&1)) | ~(DV_I_51_0_bit|DV_I_52_0_bit));
  mask &= ((((W[47]^W[49])&(1<<6))-(1<<6)) | ~(DV_I_49_2_bit|DV_I_51_2_bit));
  mask &= ((((W[48]^(W[47]>>5))&(1<<1))-(1<<1)) | ~(DV_I_47_2_bit|DV_II_51_2_bit));
  mask &= ((((W[46]^W[48])&(1<<6))-(1<<6)) | ~(DV_I_48_2_bit|DV_I_50_2_bit));
  mask &= ((((W[47]^(W[46]>>5))&(1<<1))-(1<<1)) | ~(DV_I_46_2_bit|DV_II_50_2_bit));
  mask &= ((0-((W[44]^(W[45]>>5))&(1<<1))) | ~(DV_I_51_2_bit|DV_II_49_2_bit));
  mask &= ((((W[43]^W[45])&(1<<6))-(1<<6)) | ~(DV_I_47_2_bit|DV_I_49_2_bit));
  mask &= (((((W[42]^W[44])>>6)&1)-1) | ~(DV_I_46_2_bit|DV_I_48_2_bit));
  mask &= ((((W[43]^(W[42]>>5))&(1<<1))-(1<<1)) | ~(DV_II_46_2_bit|DV_II_51_2_bit));
  mask &= ((((W[42]^(W[41]>>5))&(1<<1))-(1<<1)) | ~(DV_I_51_2_bit|DV_II_50_2_bit));
  mask &= ((((W[41]^(W[40]>>5))&(1<<1))-(1<<1)) | ~(DV_I_50_2_bit|DV_II_49_2_bit));
  if (mask & (DV_I_52_0_bit|DV_II_51_0_bit))
    mask &= ((((W[39]^(W[43]>>25))&(1<<4))-(1<<4)) | ~(DV_I_52_0_bit|DV_II_51_0_bit));
  if (mask & (DV_I_51_0_bit|DV_II_50_0_bit))
    mask &= ((((W[38]^(W[42]>>25))&(1<<4))-(1<<4)) | ~(DV_I_51_0_bit|DV_II_50_0_bit));
  if (mask & (DV_I_48_2_bit|DV_I_51_2_bit))
    mask &= ((0-((W[37]^(W[38]>>5))&(1<<1))) | ~(DV_I_48_2_bit|DV_I_51_2_bit));
  if (mask & (DV_I_50_0_bit|DV_II_49_0_bit))
    mask &= ((((W[37]^(W[41]>>25))&(1<<4))-(1<<4)) | ~(DV_I_50_0_bit|DV_II_49_0_bit));
  if (mask & (DV_II_52_0_bit|DV_II_54_0_bit))
    mask &= ((0-((W[36]^W[38])&(1<<4))) | ~(DV_II_52_0_bit|DV_II_54_0_bit));
  mask &= ((0-((W[35]^(W[36]>>5))&(1<<1))) | ~(DV_I_46_2_bit|DV_I_49_2_bit));
  if (mask & (DV_I_51_0_bit|DV_II_47_0_bit))
    mask &= ((((W[35]^(W[39]>>25))&(1<<3))-(1<<3)) | ~(DV_I_51_0_bit|DV_II_47_0_bit));
if (mask) {

  if (mask & DV_I_43_0_bit)
     if (
          !((W[61]^(W[62]>>5)) & (1<<1))
       || !(!((W[59]^(W[63]>>25)) & (1<<5)))
       || !((W[58]^(W[63]>>30)) & (1<<0))
     )  mask &= ~DV_I_43_0_bit;
  if (mask & DV_I_44_0_bit)
     if (
          !((W[62]^(W[63]>>5)) & (1<<1))
       || !(!((W[60]^(W[64]>>25)) & (1<<5)))
       || !((W[59]^(W[64]>>30)) & (1<<0))
     )  mask &= ~DV_I_44_0_bit;
  if (mask & DV_I_46_2_bit)
    mask &= ((~((W[40]^W[42])>>2)) | ~DV_I_46_2_bit);
  if (mask & DV_I_47_2_bit)
     if (
          !((W[62]^(W[63]>>5)) & (1<<2))
       || !(!((W[41]^W[43]) & (1<<6)))
     )  mask &= ~DV_I_47_2_bit;
  if (mask & DV_I_48_2_bit)
     if (
          !((W[63]^(W[64]>>5)) & (1<<2))
       || !(!((W[48]^(W[49]<<5)) & (1<<6)))
     )  mask &= ~DV_I_48_2_bit;
  if (mask & DV_I_49_2_bit)
     if (
          !(!((W[49]^(W[50]<<5)) & (1<<6)))
       || !((W[42]^W[50]) & (1<<1))
       || !(!((W[39]^(W[40]<<5)) & (1<<6)))
       || !((W[38]^W[40]) & (1<<1))
     )  mask &= ~DV_I_49_2_bit;
  if (mask & DV_I_50_0_bit)
    mask &= ((((W[36]^W[37])<<7)) | ~DV_I_50_0_bit);
  if (mask & DV_I_50_2_bit)
    mask &= ((((W[43]^W[51])<<11)) | ~DV_I_50_2_bit);
  if (mask & DV_I_51_0_bit)
    mask &= ((((W[37]^W[38])<<9)) | ~DV_I_51_0_bit);
  if (mask & DV_I_51_2_bit)
     if (
          !(!((W[51]^(W[52]<<5)) & (1<<6)))
       || !(!((W[49]^W[51]) & (1<<6)))
       || !(!((W[37]^(W[37]>>5)) & (1<<1)))
       || !(!((W[35]^(W[39]>>25)) & (1<<5)))
     )  mask &= ~DV_I_51_2_bit;
  if (mask & DV_I_52_0_bit)
    mask &= ((((W[38]^W[39])<<11)) | ~DV_I_52_0_bit);
  if (mask & DV_II_46_2_bit)
    mask &= ((((W[47]^W[51])<<17)) | ~DV_II_46_2_bit);
  if (mask & DV_II_48_0_bit)
     if (
          !(!((W[36]^(W[40]>>25)) & (1<<3)))
       || !((W[35]^(W[40]<<2)) & (1<<30))
     )  mask &= ~DV_II_48_0_bit;
  if (mask & DV_II_49_0_bit)
     if (
          !(!((W[37]^(W[41]>>25)) & (1<<3)))
       || !((W[36]^(W[41]<<2)) & (1<<30))
     )  mask &= ~DV_II_49_0_bit;
  if (mask & DV_II_49_2_bit)
     if (
          !(!((W[53]^(W[54]<<5)) & (1<<6)))
       || !(!((W[51]^W[53]) & (1<<6)))
       || !((W[50]^W[54]) & (1<<1))
       || !(!((W[45]^(W[46]<<5)) & (1<<6)))
       || !(!((W[37]^(W[41]>>25)) & (1<<5)))
       || !((W[36]^(W[41]>>30)) & (1<<0))
     )  mask &= ~DV_II_49_2_bit;
  if (mask & DV_II_50_0_bit)
     if (
          !((W[55]^W[58]) & (1<<29))
       || !(!((W[38]^(W[42]>>25)) & (1<<3)))
       || !((W[37]^(W[42]<<2)) & (1<<30))
     )  mask &= ~DV_II_50_0_bit;
  if (mask & DV_II_50_2_bit)
     if (
          !(!((W[54]^(W[55]<<5)) & (1<<6)))
       || !(!((W[52]^W[54]) & (1<<6)))
       || !((W[51]^W[55]) & (1<<1))
       || !((W[45]^W[47]) & (1<<1))
       || !(!((W[38]^(W[42]>>25)) & (1<<5)))
       || !((W[37]^(W[42]>>30)) & (1<<0))
     )  mask &= ~DV_II_50_2_bit;
  if (mask & DV_II_51_0_bit)
     if (
          !(!((W[39]^(W[43]>>25)) & (1<<3)))
       || !((W[38]^(W[43]<<2)) & (1<<30))
     )  mask &= ~DV_II_51_0_bit;
  if (mask & DV_II_51_2_bit)
     if (
          !(!((W[55]^(W[56]<<5)) & (1<<6)))
       || !(!((W[53]^W[55]) & (1<<6)))
       || !((W[52]^W[56]) & (1<<1))
       || !((W[46]^W[48]) & (1<<1))
       || !(!((W[39]^(W[43]>>25)) & (1<<5)))
       || !((W[38]^(W[43]>>30)) & (1<<0))
     )  mask &= ~DV_II_51_2_bit;
  if (mask & DV_II_52_0_bit)
     if (
          !(!((W[59]^W[60]) & (1<<29)))
       || !(!((W[40]^(W[44]>>25)) & (1<<3)))
       || !(!((W[40]^(W[44]>>25)) & (1<<4)))
       || !((W[39]^(W[44]<<2)) & (1<<30))
     )  mask &= ~DV_II_52_0_bit;
  if (mask & DV_II_53_0_bit)
     if (
          !((W[58]^W[61]) & (1<<29))
       || !(!((W[57]^(W[61]>>25)) & (1<<4)))
       || !(!((W[41]^(W[45]>>25)) & (1<<3)))
       || !(!((W[41]^(W[45]>>25)) & (1<<4)))
     )  mask &= ~DV_II_53_0_bit;
  if (mask & DV_II_54_0_bit)
     if (
          !(!((W[58]^(W[62]>>25)) & (1<<4)))
       || !(!((W[42]^(W[46]>>25)) & (1<<3)))
       || !(!((W[42]^(W[46]>>25)) & (1<<4)))
     )  mask &= ~DV_II_54_0_bit;
  if (mask & DV_II_55_0_bit)
     if (
          !(!((W[59]^(W[63]>>25)) & (1<<4)))
       || !(!((W[57]^(W[59]>>25)) & (1<<4)))
       || !(!((W[43]^(W[47]>>25)) & (1<<3)))
       || !(!((W[43]^(W[47]>>25)) & (1<<4)))
     )  mask &= ~DV_II_55_0_bit;
  if (mask & DV_II_56_0_bit)
     if (
          !(!((W[60]^(W[64]>>25)) & (1<<4)))
       || !(!((W[44]^(W[48]>>25)) & (1<<3)))
       || !(!((W[44]^(W[48]>>25)) & (1<<4)))
     )  mask &= ~DV_II_56_0_bit;
}

  dvmask[0]=mask;
}
/******************** End Of File: lib/ubc_check.c *******************/
/******************** Continue with: lib/sha1.c **********************/


#define rotate_right(x,n) (((x)>>(n))|((x)<<(32-(n))))
#define rotate_left(x,n)  (((x)<<(n))|((x)>>(32-(n))))

#define sha1_f1(b,c,d) ((d)^((b)&((c)^(d))))
#define sha1_f2(b,c,d) ((b)^(c)^(d))
#define sha1_f3(b,c,d) (((b) & ((c)|(d))) | ((c)&(d)))
#define sha1_f4(b,c,d) ((b)^(c)^(d))

#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, m, t) \
  { e += rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999 + m[t]; b = rotate_left(b, 30); }
#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, m, t) \
  { e += rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1 + m[t]; b = rotate_left(b, 30); }
#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, m, t) \
  { e += rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC + m[t]; b = rotate_left(b, 30); }
#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, m, t) \
  { e += rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6 + m[t]; b = rotate_left(b, 30); }

#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, m, t) \
  { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999 + m[t]; }
#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, m, t) \
  { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1 + m[t]; }
#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, m, t) \
  { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC + m[t]; }
#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, m, t) \
  { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6 + m[t]; }

#define SHA1_STORE_STATE(i) states[i][0] = a; states[i][1] = b; states[i][2] = c; states[i][3] = d; states[i][4] = e;

static void sha1_message_expansion(uint32_t W[80])
{
  unsigned i;
  for (i = 16; i < 80; ++i)
    W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1);
}

#if 0
static void sha1_compression(uint32_t ihv[5], const uint32_t m[16]);
static void sha1_compression(uint32_t ihv[5], const uint32_t m[16])
{
  uint32_t W[80];
  uint32_t a,b,c,d,e;
  unsigned i;

  memcpy(W, m, 16 * 4);
  for (i = 16; i < 80; ++i)
    W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1);

  a = ihv[0]; b = ihv[1]; c = ihv[2]; d = ihv[3]; e = ihv[4];

  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19);

  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39);

  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59);

  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79);

  ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e;
}
#endif


static void sha1_compression_W(uint32_t ihv[5], const uint32_t W[80])
{
  uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4];

  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18);
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19);

  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38);
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39);

  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58);
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59);

  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78);
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79);

  ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e;
}



static void sha1_compression_states(uint32_t ihv[5], const uint32_t W[80], uint32_t states[80][5])
{
  uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4];

#ifdef DOSTORESTATE00
  SHA1_STORE_STATE(0)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0);

#ifdef DOSTORESTATE01
  SHA1_STORE_STATE(1)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1);

#ifdef DOSTORESTATE02
  SHA1_STORE_STATE(2)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2);

#ifdef DOSTORESTATE03
  SHA1_STORE_STATE(3)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3);

#ifdef DOSTORESTATE04
  SHA1_STORE_STATE(4)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4);

#ifdef DOSTORESTATE05
  SHA1_STORE_STATE(5)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5);

#ifdef DOSTORESTATE06
  SHA1_STORE_STATE(6)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6);

#ifdef DOSTORESTATE07
  SHA1_STORE_STATE(7)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7);

#ifdef DOSTORESTATE08
  SHA1_STORE_STATE(8)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8);

#ifdef DOSTORESTATE09
  SHA1_STORE_STATE(9)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9);

#ifdef DOSTORESTATE10
  SHA1_STORE_STATE(10)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10);

#ifdef DOSTORESTATE11
  SHA1_STORE_STATE(11)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11);

#ifdef DOSTORESTATE12
  SHA1_STORE_STATE(12)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12);

#ifdef DOSTORESTATE13
  SHA1_STORE_STATE(13)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13);

#ifdef DOSTORESTATE14
  SHA1_STORE_STATE(14)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14);

#ifdef DOSTORESTATE15
  SHA1_STORE_STATE(15)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15);

#ifdef DOSTORESTATE16
  SHA1_STORE_STATE(16)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16);

#ifdef DOSTORESTATE17
  SHA1_STORE_STATE(17)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17);

#ifdef DOSTORESTATE18
  SHA1_STORE_STATE(18)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18);

#ifdef DOSTORESTATE19
  SHA1_STORE_STATE(19)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19);



#ifdef DOSTORESTATE20
  SHA1_STORE_STATE(20)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20);

#ifdef DOSTORESTATE21
  SHA1_STORE_STATE(21)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21);

#ifdef DOSTORESTATE22
  SHA1_STORE_STATE(22)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22);

#ifdef DOSTORESTATE23
  SHA1_STORE_STATE(23)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23);

#ifdef DOSTORESTATE24
  SHA1_STORE_STATE(24)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24);

#ifdef DOSTORESTATE25
  SHA1_STORE_STATE(25)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25);

#ifdef DOSTORESTATE26
  SHA1_STORE_STATE(26)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26);

#ifdef DOSTORESTATE27
  SHA1_STORE_STATE(27)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27);

#ifdef DOSTORESTATE28
  SHA1_STORE_STATE(28)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28);

#ifdef DOSTORESTATE29
  SHA1_STORE_STATE(29)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29);

#ifdef DOSTORESTATE30
  SHA1_STORE_STATE(30)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30);

#ifdef DOSTORESTATE31
  SHA1_STORE_STATE(31)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31);

#ifdef DOSTORESTATE32
  SHA1_STORE_STATE(32)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32);

#ifdef DOSTORESTATE33
  SHA1_STORE_STATE(33)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33);

#ifdef DOSTORESTATE34
  SHA1_STORE_STATE(34)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34);

#ifdef DOSTORESTATE35
  SHA1_STORE_STATE(35)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35);

#ifdef DOSTORESTATE36
  SHA1_STORE_STATE(36)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36);

#ifdef DOSTORESTATE37
  SHA1_STORE_STATE(37)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37);

#ifdef DOSTORESTATE38
  SHA1_STORE_STATE(38)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38);

#ifdef DOSTORESTATE39
  SHA1_STORE_STATE(39)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39);



#ifdef DOSTORESTATE40
  SHA1_STORE_STATE(40)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40);

#ifdef DOSTORESTATE41
  SHA1_STORE_STATE(41)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41);

#ifdef DOSTORESTATE42
  SHA1_STORE_STATE(42)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42);

#ifdef DOSTORESTATE43
  SHA1_STORE_STATE(43)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43);

#ifdef DOSTORESTATE44
  SHA1_STORE_STATE(44)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44);

#ifdef DOSTORESTATE45
  SHA1_STORE_STATE(45)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45);

#ifdef DOSTORESTATE46
  SHA1_STORE_STATE(46)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46);

#ifdef DOSTORESTATE47
  SHA1_STORE_STATE(47)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47);

#ifdef DOSTORESTATE48
  SHA1_STORE_STATE(48)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48);

#ifdef DOSTORESTATE49
  SHA1_STORE_STATE(49)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49);

#ifdef DOSTORESTATE50
  SHA1_STORE_STATE(50)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50);

#ifdef DOSTORESTATE51
  SHA1_STORE_STATE(51)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51);

#ifdef DOSTORESTATE52
  SHA1_STORE_STATE(52)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52);

#ifdef DOSTORESTATE53
  SHA1_STORE_STATE(53)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53);

#ifdef DOSTORESTATE54
  SHA1_STORE_STATE(54)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54);

#ifdef DOSTORESTATE55
  SHA1_STORE_STATE(55)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55);

#ifdef DOSTORESTATE56
  SHA1_STORE_STATE(56)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56);

#ifdef DOSTORESTATE57
  SHA1_STORE_STATE(57)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57);

#ifdef DOSTORESTATE58
  SHA1_STORE_STATE(58)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58);

#ifdef DOSTORESTATE59
  SHA1_STORE_STATE(59)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59);




#ifdef DOSTORESTATE60
  SHA1_STORE_STATE(60)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60);

#ifdef DOSTORESTATE61
  SHA1_STORE_STATE(61)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61);

#ifdef DOSTORESTATE62
  SHA1_STORE_STATE(62)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62);

#ifdef DOSTORESTATE63
  SHA1_STORE_STATE(63)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63);

#ifdef DOSTORESTATE64
  SHA1_STORE_STATE(64)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64);

#ifdef DOSTORESTATE65
  SHA1_STORE_STATE(65)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65);

#ifdef DOSTORESTATE66
  SHA1_STORE_STATE(66)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66);

#ifdef DOSTORESTATE67
  SHA1_STORE_STATE(67)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67);

#ifdef DOSTORESTATE68
  SHA1_STORE_STATE(68)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68);

#ifdef DOSTORESTATE69
  SHA1_STORE_STATE(69)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69);

#ifdef DOSTORESTATE70
  SHA1_STORE_STATE(70)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70);

#ifdef DOSTORESTATE71
  SHA1_STORE_STATE(71)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71);

#ifdef DOSTORESTATE72
  SHA1_STORE_STATE(72)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72);

#ifdef DOSTORESTATE73
  SHA1_STORE_STATE(73)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73);

#ifdef DOSTORESTATE74
  SHA1_STORE_STATE(74)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74);

#ifdef DOSTORESTATE75
  SHA1_STORE_STATE(75)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75);

#ifdef DOSTORESTATE76
  SHA1_STORE_STATE(76)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76);

#ifdef DOSTORESTATE77
  SHA1_STORE_STATE(77)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77);

#ifdef DOSTORESTATE78
  SHA1_STORE_STATE(78)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78);

#ifdef DOSTORESTATE79
  SHA1_STORE_STATE(79)
#endif
  HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79);



  ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e;
}




#define SHA1_RECOMPRESS(t) \
void sha1recompress_fast_ ## t (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) \
{ \
  uint32_t a = state[0], b = state[1], c = state[2], d = state[3], e = state[4]; \
  if (t > 79) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 79); \
  if (t > 78) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 78); \
  if (t > 77) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 77); \
  if (t > 76) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 76); \
  if (t > 75) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 75); \
  if (t > 74) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 74); \
  if (t > 73) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 73); \
  if (t > 72) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 72); \
  if (t > 71) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 71); \
  if (t > 70) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 70); \
  if (t > 69) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 69); \
  if (t > 68) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 68); \
  if (t > 67) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 67); \
  if (t > 66) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 66); \
  if (t > 65) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 65); \
  if (t > 64) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 64); \
  if (t > 63) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 63); \
  if (t > 62) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 62); \
  if (t > 61) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 61); \
  if (t > 60) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 60); \
  if (t > 59) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 59); \
  if (t > 58) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 58); \
  if (t > 57) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 57); \
  if (t > 56) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 56); \
  if (t > 55) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 55); \
  if (t > 54) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 54); \
  if (t > 53) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 53); \
  if (t > 52) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 52); \
  if (t > 51) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 51); \
  if (t > 50) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 50); \
  if (t > 49) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 49); \
  if (t > 48) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 48); \
  if (t > 47) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 47); \
  if (t > 46) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 46); \
  if (t > 45) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 45); \
  if (t > 44) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 44); \
  if (t > 43) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 43); \
  if (t > 42) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 42); \
  if (t > 41) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 41); \
  if (t > 40) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 40); \
  if (t > 39) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 39); \
  if (t > 38) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 38); \
  if (t > 37) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 37); \
  if (t > 36) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 36); \
  if (t > 35) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 35); \
  if (t > 34) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 34); \
  if (t > 33) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 33); \
  if (t > 32) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 32); \
  if (t > 31) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 31); \
  if (t > 30) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 30); \
  if (t > 29) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 29); \
  if (t > 28) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 28); \
  if (t > 27) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 27); \
  if (t > 26) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 26); \
  if (t > 25) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 25); \
  if (t > 24) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 24); \
  if (t > 23) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 23); \
  if (t > 22) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 22); \
  if (t > 21) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 21); \
  if (t > 20) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 20); \
  if (t > 19) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 19); \
  if (t > 18) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 18); \
  if (t > 17) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 17); \
  if (t > 16) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 16); \
  if (t > 15) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 15); \
  if (t > 14) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 14); \
  if (t > 13) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 13); \
  if (t > 12) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 12); \
  if (t > 11) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 11); \
  if (t > 10) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 10); \
  if (t > 9) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 9); \
  if (t > 8) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 8); \
  if (t > 7) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 7); \
  if (t > 6) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 6); \
  if (t > 5) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 5); \
  if (t > 4) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 4); \
  if (t > 3) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 3); \
  if (t > 2) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 2); \
  if (t > 1) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 1); \
  if (t > 0) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 0); \
  ihvin[0] = a; ihvin[1] = b; ihvin[2] = c; ihvin[3] = d; ihvin[4] = e; \
  a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4]; \
  if (t <= 0) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 0); \
  if (t <= 1) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 1); \
  if (t <= 2) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 2); \
  if (t <= 3) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 3); \
  if (t <= 4) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 4); \
  if (t <= 5) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 5); \
  if (t <= 6) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 6); \
  if (t <= 7) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 7); \
  if (t <= 8) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 8); \
  if (t <= 9) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 9); \
  if (t <= 10) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 10); \
  if (t <= 11) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 11); \
  if (t <= 12) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 12); \
  if (t <= 13) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 13); \
  if (t <= 14) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 14); \
  if (t <= 15) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 15); \
  if (t <= 16) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 16); \
  if (t <= 17) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 17); \
  if (t <= 18) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 18); \
  if (t <= 19) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 19); \
  if (t <= 20) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 20); \
  if (t <= 21) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 21); \
  if (t <= 22) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 22); \
  if (t <= 23) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 23); \
  if (t <= 24) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 24); \
  if (t <= 25) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 25); \
  if (t <= 26) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 26); \
  if (t <= 27) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 27); \
  if (t <= 28) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 28); \
  if (t <= 29) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 29); \
  if (t <= 30) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 30); \
  if (t <= 31) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 31); \
  if (t <= 32) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 32); \
  if (t <= 33) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 33); \
  if (t <= 34) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 34); \
  if (t <= 35) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 35); \
  if (t <= 36) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 36); \
  if (t <= 37) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 37); \
  if (t <= 38) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 38); \
  if (t <= 39) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 39); \
  if (t <= 40) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 40); \
  if (t <= 41) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 41); \
  if (t <= 42) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 42); \
  if (t <= 43) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 43); \
  if (t <= 44) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 44); \
  if (t <= 45) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 45); \
  if (t <= 46) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 46); \
  if (t <= 47) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 47); \
  if (t <= 48) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 48); \
  if (t <= 49) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 49); \
  if (t <= 50) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 50); \
  if (t <= 51) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 51); \
  if (t <= 52) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 52); \
  if (t <= 53) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 53); \
  if (t <= 54) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 54); \
  if (t <= 55) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 55); \
  if (t <= 56) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 56); \
  if (t <= 57) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 57); \
  if (t <= 58) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 58); \
  if (t <= 59) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 59); \
  if (t <= 60) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 60); \
  if (t <= 61) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 61); \
  if (t <= 62) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 62); \
  if (t <= 63) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 63); \
  if (t <= 64) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 64); \
  if (t <= 65) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 65); \
  if (t <= 66) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 66); \
  if (t <= 67) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 67); \
  if (t <= 68) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 68); \
  if (t <= 69) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 69); \
  if (t <= 70) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 70); \
  if (t <= 71) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 71); \
  if (t <= 72) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 72); \
  if (t <= 73) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 73); \
  if (t <= 74) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 74); \
  if (t <= 75) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 75); \
  if (t <= 76) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 76); \
  if (t <= 77) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 77); \
  if (t <= 78) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 78); \
  if (t <= 79) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 79); \
  ihvout[0] = ihvin[0] + a; ihvout[1] = ihvin[1] + b; ihvout[2] = ihvin[2] + c; ihvout[3] = ihvin[3] + d; ihvout[4] = ihvin[4] + e; \
}

SHA1_RECOMPRESS(0)
SHA1_RECOMPRESS(1)
SHA1_RECOMPRESS(2)
SHA1_RECOMPRESS(3)
SHA1_RECOMPRESS(4)
SHA1_RECOMPRESS(5)
SHA1_RECOMPRESS(6)
SHA1_RECOMPRESS(7)
SHA1_RECOMPRESS(8)
SHA1_RECOMPRESS(9)

SHA1_RECOMPRESS(10)
SHA1_RECOMPRESS(11)
SHA1_RECOMPRESS(12)
SHA1_RECOMPRESS(13)
SHA1_RECOMPRESS(14)
SHA1_RECOMPRESS(15)
SHA1_RECOMPRESS(16)
SHA1_RECOMPRESS(17)
SHA1_RECOMPRESS(18)
SHA1_RECOMPRESS(19)

SHA1_RECOMPRESS(20)
SHA1_RECOMPRESS(21)
SHA1_RECOMPRESS(22)
SHA1_RECOMPRESS(23)
SHA1_RECOMPRESS(24)
SHA1_RECOMPRESS(25)
SHA1_RECOMPRESS(26)
SHA1_RECOMPRESS(27)
SHA1_RECOMPRESS(28)
SHA1_RECOMPRESS(29)

SHA1_RECOMPRESS(30)
SHA1_RECOMPRESS(31)
SHA1_RECOMPRESS(32)
SHA1_RECOMPRESS(33)
SHA1_RECOMPRESS(34)
SHA1_RECOMPRESS(35)
SHA1_RECOMPRESS(36)
SHA1_RECOMPRESS(37)
SHA1_RECOMPRESS(38)
SHA1_RECOMPRESS(39)

SHA1_RECOMPRESS(40)
SHA1_RECOMPRESS(41)
SHA1_RECOMPRESS(42)
SHA1_RECOMPRESS(43)
SHA1_RECOMPRESS(44)
SHA1_RECOMPRESS(45)
SHA1_RECOMPRESS(46)
SHA1_RECOMPRESS(47)
SHA1_RECOMPRESS(48)
SHA1_RECOMPRESS(49)

SHA1_RECOMPRESS(50)
SHA1_RECOMPRESS(51)
SHA1_RECOMPRESS(52)
SHA1_RECOMPRESS(53)
SHA1_RECOMPRESS(54)
SHA1_RECOMPRESS(55)
SHA1_RECOMPRESS(56)
SHA1_RECOMPRESS(57)
SHA1_RECOMPRESS(58)
SHA1_RECOMPRESS(59)

SHA1_RECOMPRESS(60)
SHA1_RECOMPRESS(61)
SHA1_RECOMPRESS(62)
SHA1_RECOMPRESS(63)
SHA1_RECOMPRESS(64)
SHA1_RECOMPRESS(65)
SHA1_RECOMPRESS(66)
SHA1_RECOMPRESS(67)
SHA1_RECOMPRESS(68)
SHA1_RECOMPRESS(69)

SHA1_RECOMPRESS(70)
SHA1_RECOMPRESS(71)
SHA1_RECOMPRESS(72)
SHA1_RECOMPRESS(73)
SHA1_RECOMPRESS(74)
SHA1_RECOMPRESS(75)
SHA1_RECOMPRESS(76)
SHA1_RECOMPRESS(77)
SHA1_RECOMPRESS(78)
SHA1_RECOMPRESS(79)

static sha1_recompression_type sha1_recompression_step[80] =
{
  sha1recompress_fast_0, sha1recompress_fast_1, sha1recompress_fast_2, sha1recompress_fast_3, sha1recompress_fast_4, sha1recompress_fast_5, sha1recompress_fast_6, sha1recompress_fast_7, sha1recompress_fast_8, sha1recompress_fast_9,
  sha1recompress_fast_10, sha1recompress_fast_11, sha1recompress_fast_12, sha1recompress_fast_13, sha1recompress_fast_14, sha1recompress_fast_15, sha1recompress_fast_16, sha1recompress_fast_17, sha1recompress_fast_18, sha1recompress_fast_19,
  sha1recompress_fast_20, sha1recompress_fast_21, sha1recompress_fast_22, sha1recompress_fast_23, sha1recompress_fast_24, sha1recompress_fast_25, sha1recompress_fast_26, sha1recompress_fast_27, sha1recompress_fast_28, sha1recompress_fast_29,
  sha1recompress_fast_30, sha1recompress_fast_31, sha1recompress_fast_32, sha1recompress_fast_33, sha1recompress_fast_34, sha1recompress_fast_35, sha1recompress_fast_36, sha1recompress_fast_37, sha1recompress_fast_38, sha1recompress_fast_39,
  sha1recompress_fast_40, sha1recompress_fast_41, sha1recompress_fast_42, sha1recompress_fast_43, sha1recompress_fast_44, sha1recompress_fast_45, sha1recompress_fast_46, sha1recompress_fast_47, sha1recompress_fast_48, sha1recompress_fast_49,
  sha1recompress_fast_50, sha1recompress_fast_51, sha1recompress_fast_52, sha1recompress_fast_53, sha1recompress_fast_54, sha1recompress_fast_55, sha1recompress_fast_56, sha1recompress_fast_57, sha1recompress_fast_58, sha1recompress_fast_59,
  sha1recompress_fast_60, sha1recompress_fast_61, sha1recompress_fast_62, sha1recompress_fast_63, sha1recompress_fast_64, sha1recompress_fast_65, sha1recompress_fast_66, sha1recompress_fast_67, sha1recompress_fast_68, sha1recompress_fast_69,
  sha1recompress_fast_70, sha1recompress_fast_71, sha1recompress_fast_72, sha1recompress_fast_73, sha1recompress_fast_74, sha1recompress_fast_75, sha1recompress_fast_76, sha1recompress_fast_77, sha1recompress_fast_78, sha1recompress_fast_79,
};

static void sha1_process(fsl_sha1_cx* ctx, const uint32_t block[16])
{
  unsigned i, j;
  uint32_t ubc_dv_mask[DVMASKSIZE];
  uint32_t ihvtmp[5];
  for (i=0; i < DVMASKSIZE; ++i)
    ubc_dv_mask[i]=0;
  ctx->ihv1[0] = ctx->ihv[0];
  ctx->ihv1[1] = ctx->ihv[1];
  ctx->ihv1[2] = ctx->ihv[2];
  ctx->ihv1[3] = ctx->ihv[3];
  ctx->ihv1[4] = ctx->ihv[4];
  memcpy(ctx->m1, block, 64);
  sha1_message_expansion(ctx->m1);
  if (ctx->detect_coll && ctx->ubc_check)
  {
    ubc_check(ctx->m1, ubc_dv_mask);
  }
  sha1_compression_states(ctx->ihv, ctx->m1, ctx->states);
  if (ctx->detect_coll)
  {
    for (i = 0; sha1_dvs[i].dvType != 0; ++i)
    {
      if ((0 == ctx->ubc_check) || (((uint32_t)(1) << sha1_dvs[i].maskb) & ubc_dv_mask[sha1_dvs[i].maski]))
      {
        for (j = 0; j < 80; ++j)
          ctx->m2[j] = ctx->m1[j] ^ sha1_dvs[i].dm[j];
        (sha1_recompression_step[sha1_dvs[i].testt])(ctx->ihv2, ihvtmp, ctx->m2, ctx->states[sha1_dvs[i].testt]);
        /* to verify SHA-1 collision detection code with collisions for reduced-step SHA-1 */
        if ((ihvtmp[0] == ctx->ihv[0] && ihvtmp[1] == ctx->ihv[1] && ihvtmp[2] == ctx->ihv[2] && ihvtmp[3] == ctx->ihv[3] && ihvtmp[4] == ctx->ihv[4])
          || (ctx->reduced_round_coll && ctx->ihv1[0] == ctx->ihv2[0] && ctx->ihv1[1] == ctx->ihv2[1] && ctx->ihv1[2] == ctx->ihv2[2] && ctx->ihv1[3] == ctx->ihv2[3] && ctx->ihv1[4] == ctx->ihv2[4]))
        {
          ctx->found_collision = 1;
          /* TODO: call callback */
          if (ctx->callback != NULL)
            ctx->callback(ctx->total - 64, ctx->ihv1, ctx->ihv2, ctx->m1, ctx->m2);

          if (ctx->safe_hash)
          {
            sha1_compression_W(ctx->ihv, ctx->m1);
            sha1_compression_W(ctx->ihv, ctx->m1);
          }

          break;
        }
      }
    }
  }
}

static void swap_bytes(uint32_t val[16])
{
  unsigned i;
  for (i = 0; i < 16; ++i)
  {
    val[i] = ((val[i] << 8) & 0xFF00FF00) | ((val[i] >> 8) & 0xFF00FF);
    val[i] = (val[i] << 16) | (val[i] >> 16);
  }
}


static const unsigned char sha1_padding[64] =
{
  0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

#undef DVMASKSIZE
#undef DOSTORESTATE58
#undef DOSTORESTATE65
#undef rotate_right
#undef rotate_left
#undef sha1_f1
#undef sha1_f2
#undef sha1_f3
#undef sha1_f4
#undef HASHCLASH_SHA1COMPRESS_ROUND1_STEP
#undef HASHCLASH_SHA1COMPRESS_ROUND2_STEP
#undef HASHCLASH_SHA1COMPRESS_ROUND3_STEP
#undef HASHCLASH_SHA1COMPRESS_ROUND4_STEP
#undef HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW
#undef HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW
#undef HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW
#undef HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW
#undef SHA1_STORE_STATE
#undef SHA1_RECOMPRESS

/************************************************************************/
/* End FSL_SHA1_HARDENED */
/************************************************************************/
#else /* end FSL_SHA1_HARDENED */
/************************************************************************/
/* Standard SHA1... */
/************************************************************************/

/*
   The SHA1 implementation below is adapted from:
  
    $NetBSD: sha1.c,v 1.6 2009/11/06 20:31:18 joerg Exp $
    $OpenBSD: sha1.c,v 1.9 1997/07/23 21:12:32 kstailey Exp $
  
   SHA-1 in C
   By Steve Reid <steve@edmweb.com>
   100% Public Domain
*/

/*
 * blk0() and blk() perform the initial expand.
 * I got the idea of expanding during the round function from SSLeay
 *
 * blk0le() for little-endian and blk0be() for big-endian.
 */
#if 0 && __GNUC__ && (defined(__i386__) || defined(__x86_64__))
/*
 * GCC by itself only generates left rotates.  Use right rotates if
 * possible to be kinder to dinky implementations with iterative rotate
 * instructions.
 */
#define SHA_ROT(op, x, k)                                               \
  ({ unsigned int y; asm(op " %1,%0" : "=r" (y) : "I" (k), "0" (x)); y; })
#define rol(x,k) SHA_ROT("roll", x, k)
#define ror(x,k) SHA_ROT("rorl", x, k)

#else
/* Generic C equivalent */
#define SHA_ROT(x,l,r) ((x) << (l) | (x) >> (r))
#define rol(x,k) SHA_ROT(x,k,32-(k))
#define ror(x,k) SHA_ROT(x,32-(k),k)
#endif


#define blk0le(i) (block[i] = (ror(block[i],8)&0xFF00FF00)  \
                   |(rol(block[i],8)&0x00FF00FF))
#define blk0be(i) block[i]
#define blk(i) (block[i&15] = rol(block[(i+13)&15]^block[(i+8)&15]  \
                                  ^block[(i+2)&15]^block[i&15],1))

/*
 * (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1
 *
 * Rl0() for little-endian and Rb0() for big-endian.  Endianness is 
 * determined at run-time.
 */
#define Rl0(v,w,x,y,z,i)                                        \
  z+=((w&(x^y))^y)+blk0le(i)+0x5A827999+rol(v,5);w=ror(w,2);
#define Rb0(v,w,x,y,z,i)                                        \
  z+=((w&(x^y))^y)+blk0be(i)+0x5A827999+rol(v,5);w=ror(w,2);
#define R1(v,w,x,y,z,i)                                     \
  z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=ror(w,2);
#define R2(v,w,x,y,z,i)                             \
  z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=ror(w,2);
#define R3(v,w,x,y,z,i)                                         \
  z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=ror(w,2);
#define R4(v,w,x,y,z,i)                             \
  z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=ror(w,2);

/*
 * Hash a single 512-bit block. This is the core of the algorithm.
 */
#define a qq[0]
#define b qq[1]
#define c qq[2]
#define d qq[3]
#define e qq[4]

static void SHA1Transform(unsigned int state[5], const unsigned char buffer[64])
{
  unsigned int qq[5]; /* a, b, c, d, e; */
  static int one = 1;
  unsigned int block[16];
  memcpy(block, buffer, 64);
  memcpy(qq,state,5*sizeof(unsigned int));

  /* Copy context->state[] to working vars */
  /*
    a = state[0];
    b = state[1];
    c = state[2];
    d = state[3];
    e = state[4];
  */

  /* 4 rounds of 20 operations each. Loop unrolled. */
  if( 1 == *(unsigned char*)&one ){
    Rl0(a,b,c,d,e, 0); Rl0(e,a,b,c,d, 1); Rl0(d,e,a,b,c, 2); Rl0(c,d,e,a,b, 3);
    Rl0(b,c,d,e,a, 4); Rl0(a,b,c,d,e, 5); Rl0(e,a,b,c,d, 6); Rl0(d,e,a,b,c, 7);
    Rl0(c,d,e,a,b, 8); Rl0(b,c,d,e,a, 9); Rl0(a,b,c,d,e,10); Rl0(e,a,b,c,d,11);
    Rl0(d,e,a,b,c,12); Rl0(c,d,e,a,b,13); Rl0(b,c,d,e,a,14); Rl0(a,b,c,d,e,15);
  }else{
    Rb0(a,b,c,d,e, 0); Rb0(e,a,b,c,d, 1); Rb0(d,e,a,b,c, 2); Rb0(c,d,e,a,b, 3);
    Rb0(b,c,d,e,a, 4); Rb0(a,b,c,d,e, 5); Rb0(e,a,b,c,d, 6); Rb0(d,e,a,b,c, 7);
    Rb0(c,d,e,a,b, 8); Rb0(b,c,d,e,a, 9); Rb0(a,b,c,d,e,10); Rb0(e,a,b,c,d,11);
    Rb0(d,e,a,b,c,12); Rb0(c,d,e,a,b,13); Rb0(b,c,d,e,a,14); Rb0(a,b,c,d,e,15);
  }
  R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
  R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
  R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
  R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
  R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
  R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
  R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
  R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
  R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
  R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
  R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
  R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
  R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
  R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
  R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
  R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);

  /* Add the working vars back into context.state[] */
  state[0] += a;
  state[1] += b;
  state[2] += c;
  state[3] += d;
  state[4] += e;
}

/*
   The state of a incremental SHA1 checksum computation.  Only one
   such computation can be underway at a time, of course.
*/
/* static fsl_sha1_cx incrCtx; */

#if 0
/*
   Add more text to the incremental SHA1 checksum.
*/
static void fsl_sha1sum_step_text(fsl_sha1_cx * cx, const char *zText, int nBytes){
  if( nBytes<=0 ){
    if( nBytes==0 ) return;
    nBytes = fsl_strlen(zText);
  }
  fsl_sha1_update(cx, (unsigned char*)zText, nBytes);
}
#endif

#if 0
/*
   Add the content of a blob to the incremental SHA1 checksum.
*/
static void fsl_sha1sum_step_buffer(fsl_sha1_cx * cx, fsl_buffer *b){
  fsl_sha1sum_step_text(cx, fsl_buffer_cstr(b), b->used);
}
#endif

#undef SHA_ROT
#undef rol
#undef ror
#undef blk0le
#undef blk0be
#undef blk

#undef Rl0
#undef Rb0
#undef R1
#undef R2
#undef R3
#undef R4
#undef a
#undef b
#undef c
#undef d
#undef e
#endif /* Standard SHA1 */
/************************************************************************/
/* End main hash-type-specific code. Common code follows... */
/************************************************************************/

void fsl_sha1_init(fsl_sha1_cx *ctx){
  /* SHA1 initialization constants */
#if FSL_SHA1_HARDENED
  static const union { unsigned char bytes[4]; uint32_t value; } endianness = { { 0, 1, 2, 3 } };
  static const uint32_t littleendian = 0x03020100;
  memset(ctx, 0, sizeof(*ctx));
  ctx->total = 0;
  ctx->ihv[0] = 0x67452301;
  ctx->ihv[1] = 0xEFCDAB89;
  ctx->ihv[2] = 0x98BADCFE;
  ctx->ihv[3] = 0x10325476;
  ctx->ihv[4] = 0xC3D2E1F0;
  ctx->found_collision = 0;
  ctx->safe_hash = 1;
  ctx->ubc_check = 1;
  ctx->detect_coll = 1;
  ctx->reduced_round_coll = 0;
  ctx->bigendian = (endianness.value != littleendian);
  ctx->callback = NULL;
#else
  *ctx = fsl_sha1_cx_empty;
#endif
}


/*
 * Run your data through this.
 */
void fsl_sha1_update( fsl_sha1_cx *ctx, void const * data_,
                      fsl_size_t len ){
#if FSL_SHA1_HARDENED
  const unsigned char *buf = (const unsigned char *)data_;
  unsigned left, fill;
  if (len == 0)
    return;
  left = ctx->total & 63;
  fill = 64 - left;
  if (left && len >= fill)
  {
    ctx->total += fill;
    memcpy(ctx->buffer + left, buf, fill);
    if (!ctx->bigendian)
      swap_bytes((uint32_t*)(ctx->buffer));
    sha1_process(ctx, (uint32_t*)(ctx->buffer));
    buf += fill;
    len -= fill;
    left = 0;
  }
  while (len >= 64)
  {
    ctx->total += 64;
    if (!ctx->bigendian)
    {
      memcpy(ctx->buffer, buf, 64);
      swap_bytes((uint32_t*)(ctx->buffer));
      sha1_process(ctx, (uint32_t*)(ctx->buffer));
    }
    else
      sha1_process(ctx, (uint32_t*)(buf));
    buf += 64;
    len -= 64;
  }
  if (len > 0)
  {
    ctx->total += len;
    memcpy(ctx->buffer + left, buf, len);
  }
#else
  const unsigned char *data = (const unsigned char *)data_;
  unsigned int i, j;
  j = ctx->count[0];
  if ((ctx->count[0] += len << 3) < j)
	ctx->count[1] += (len>>29)+1;
  j = (j >> 3) & 63;
  if ((j + len) > 63) {
    memcpy(&ctx->buffer[j], data, (i = 64-j));
    SHA1Transform(ctx->state, ctx->buffer);
    for ( ; i + 63 < len; i += 64)
      SHA1Transform(ctx->state, &data[i]);
    j = 0;
  } else {
    i = 0;
  }
  (void)memcpy(&ctx->buffer[j], &data[i], len - i);
#endif
}

/*
   Convert a digest into base-16.  digest should be declared as
   "unsigned char digest[20]" in the calling function.  The SHA1
   digest is stored in the first 20 bytes.  zBuf should
   be "char zBuf[41]".
*/
void fsl_sha1_digest_to_base16(unsigned char *digest, char *zBuf){
  static char const zEncode[] = "0123456789abcdef";
  int ix;

  for(ix=0; ix<FSL_STRLEN_SHA1/2; ix++){
    *zBuf++ = zEncode[(*digest>>4)&0xf];
    *zBuf++ = zEncode[*digest++ & 0xf];
  }
  *zBuf = '\0';
}



int fsl_sha1_final(fsl_sha1_cx *ctx, unsigned char * digest){
#if FSL_SHA1_HARDENED
  uint32_t last = ctx->total & 63;
  uint32_t padn = (last < 56) ? (56 - last) : (120 - last);
  uint64_t total;
  fsl_sha1_update(ctx, sha1_padding, padn);

  total = ctx->total - padn;
  total <<= 3;
  ctx->buffer[56] = (unsigned char)(total >> 56);
  ctx->buffer[57] = (unsigned char)(total >> 48);
  ctx->buffer[58] = (unsigned char)(total >> 40);
  ctx->buffer[59] = (unsigned char)(total >> 32);
  ctx->buffer[60] = (unsigned char)(total >> 24);
  ctx->buffer[61] = (unsigned char)(total >> 16);
  ctx->buffer[62] = (unsigned char)(total >> 8);
  ctx->buffer[63] = (unsigned char)(total);
  if (!ctx->bigendian)
    swap_bytes((uint32_t*)(ctx->buffer));
  sha1_process(ctx, (uint32_t*)(ctx->buffer));
  digest[0] = (unsigned char)(ctx->ihv[0] >> 24);
  digest[1] = (unsigned char)(ctx->ihv[0] >> 16);
  digest[2] = (unsigned char)(ctx->ihv[0] >> 8);
  digest[3] = (unsigned char)(ctx->ihv[0]);
  digest[4] = (unsigned char)(ctx->ihv[1] >> 24);
  digest[5] = (unsigned char)(ctx->ihv[1] >> 16);
  digest[6] = (unsigned char)(ctx->ihv[1] >> 8);
  digest[7] = (unsigned char)(ctx->ihv[1]);
  digest[8] = (unsigned char)(ctx->ihv[2] >> 24);
  digest[9] = (unsigned char)(ctx->ihv[2] >> 16);
  digest[10] = (unsigned char)(ctx->ihv[2] >> 8);
  digest[11] = (unsigned char)(ctx->ihv[2]);
  digest[12] = (unsigned char)(ctx->ihv[3] >> 24);
  digest[13] = (unsigned char)(ctx->ihv[3] >> 16);
  digest[14] = (unsigned char)(ctx->ihv[3] >> 8);
  digest[15] = (unsigned char)(ctx->ihv[3]);
  digest[16] = (unsigned char)(ctx->ihv[4] >> 24);
  digest[17] = (unsigned char)(ctx->ihv[4] >> 16);
  digest[18] = (unsigned char)(ctx->ihv[4] >> 8);
  digest[19] = (unsigned char)(ctx->ihv[4]);
  return ctx->found_collision;
#else
  unsigned int i;
  unsigned char finalcount[8];
  for (i = 0; i < 8; i++) {
	finalcount[i] = (unsigned char)((ctx->count[(i >= 4 ? 0 : 1)]
                                     >> ((3-(i & 3)) * 8) ) & 255);	 /* Endian independent */
  }
  fsl_sha1_update(ctx, (const unsigned char *)"\200", 1);
  while ((ctx->count[0] & 504) != 448){
    fsl_sha1_update(ctx, (const unsigned char *)"\0", 1);
  }
  fsl_sha1_update(ctx, finalcount, 8);  /* Should cause a SHA1Transform() */
  if (digest) {
    for (i = 0; i < 20; i++){
      digest[i] = (unsigned char)
        ((ctx->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
    }
  }
  return 0;
#endif
}

char const * fsl_sha1_final_hex(fsl_sha1_cx *context, char * zHex){
  unsigned char zResult[FSL_STRLEN_SHA1/2];
  fsl_sha1_final(context, zResult);
  fsl_sha1_digest_to_base16(zResult, zHex);
  return (char const *)zHex;
}

int fsl_sha1sum_stream(fsl_input_f src, void * srcState, fsl_buffer *pCksum){
  enum { BufSize = 1024 * 4 };
  fsl_sha1_cx ctx;
  int rc;
  unsigned char zBuf[BufSize];
  if(!src || !pCksum) return FSL_RC_MISUSE;
  fsl_sha1_init(&ctx);
  for(;;){
    fsl_size_t read = (fsl_size_t)BufSize;
    rc = src(srcState, zBuf, &read);
    if(rc) return rc;
    else if(read) fsl_sha1_update(&ctx, (unsigned char*)zBuf, read);
    if(read < (fsl_size_t)BufSize) break;
  }
  fsl_buffer_reuse(pCksum);
  rc = fsl_buffer_reserve(pCksum, FSL_STRLEN_SHA1+1/*NUL*/);
  /*^^^^ DO NOT fsl_buffer_resize(), as pCksum is, more often than not,
    a cached reused buffer. */
  if(!rc){
    fsl_sha1_final_hex(&ctx, fsl_buffer_str(pCksum));
    pCksum->used = (fsl_size_t)FSL_STRLEN_SHA1;
    pCksum->mem[pCksum->used] = 0;
  }
  return rc;
}

int fsl_sha1sum_filename(const char *zFilename, fsl_buffer *pCksum){
  if(!zFilename || !pCksum) return FSL_RC_MISUSE;
  else{
#if 1
    int rc;
    FILE *in = fsl_fopen(zFilename, "rb");
    if(!in) rc = FSL_RC_IO;
    else{
      rc = fsl_sha1sum_stream(fsl_input_f_FILE, in, pCksum);
      fsl_fclose(in);
    }
    return rc;
#else
    /* Requires v1 code which has not yet been ported in. */
    FILE *in;
    fsl_sha1_cx ctx;
    unsigned char zResult[FSL_STRLEN_SHA1/2];
    char zBuf[10240];

    if( fsl_wd_islink(zFilename) ){
      /* Instead of file content, return sha1 of link destination path */
      Blob destinationPath;
      int rc;
      
      blob_read_link(&destinationPath, zFilename);
      rc = sha1sum_blob(&destinationPath, pCksum);
      blob_reset(&destinationPath);
      return rc;
    }

    in = fossil_fopen(zFilename,"rb");
    if( in==0 ){
      return 1;
    }
    fsl_sha1_init(&ctx);
    for(;;){
      int n;
      n = fread(zBuf, 1, sizeof(zBuf), in);
      if( n<=0 ) break;
      fsl_sha1_update(&ctx, (unsigned char*)zBuf, (unsigned)n);
    }
    fclose_fclose(in);
    blob_zero(pCksum);
    blob_resize(pCksum, FSL_STRLEN_SHA1);
    fsl_sha1_final(&ctx, zResult);
    fsl_sha1_digest_to_base16(zResult, blob_buffer(pCksum));
    return 0;
#endif
  }
}

int fsl_sha1sum_buffer(fsl_buffer const *pIn, fsl_buffer *pCksum){
  if(!pIn || !pCksum) return FSL_RC_MISUSE;
  else{
    fsl_sha1_cx ctx;
    int rc;
    fsl_sha1_init(&ctx);
    fsl_sha1_update(&ctx, pIn->mem, pIn->used);
    rc = fsl_buffer_reserve(pCksum, FSL_STRLEN_SHA1+1/*NUL*/);
    if(!rc){
      fsl_buffer_reuse(pCksum);
      fsl_sha1_final_hex(&ctx, fsl_buffer_str(pCksum));
      pCksum->used = (fsl_size_t)FSL_STRLEN_SHA1;
      pCksum->mem[pCksum->used] = 0;
    }
    return rc;
  }
}

char *fsl_sha1sum_cstr(const char *zIn, fsl_int_t len){
  if(!zIn || !len) return NULL;
  else{
    fsl_sha1_cx ctx;
    char * zHex = (char *)fsl_malloc(FSL_STRLEN_SHA1+1);
    if(!zHex) return NULL;
    fsl_sha1_init(&ctx);
    fsl_sha1_update(&ctx, zIn,
                    (len<0) ? fsl_strlen(zIn) : (fsl_size_t)len);
    fsl_sha1_final_hex(&ctx, zHex);
    return zHex;
  }
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/sha3.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
** Copyright (c) 2017 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains an implementation of SHA3 (Keccak) hashing.
*/
/**
   This copy was modified slightly for use with the libfossil API.
*/
#include "fossil-scm/fossil.h"
#include "fossil-scm/fossil-hash.h"
#include <assert.h>
#include <string.h> /* strlen() */
#include <stddef.h> /* NULL on linux */
#include <sys/types.h>
#include <assert.h>

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

/*
** Macros to determine whether the machine is big or little endian,
** and whether or not that determination is run-time or compile-time.
**
** For best performance, an attempt is made to guess at the byte-order
** using C-preprocessor macros.  If that is unsuccessful, or if
** -DSHA3_BYTEORDER=0 is set, then byte-order is determined
** at run-time.
*/
#ifndef SHA3_BYTEORDER
# if defined(i386)     || defined(__i386__)   || defined(_M_IX86) ||    \
     defined(__x86_64) || defined(__x86_64__) || defined(_M_X64)  ||    \
     defined(_M_AMD64) || defined(_M_ARM)     || defined(__x86)   ||    \
     defined(__arm__)
#   define SHA3_BYTEORDER    1234
# elif defined(sparc)    || defined(__ppc__)
#   define SHA3_BYTEORDER    4321
# else
#   define SHA3_BYTEORDER 0
# endif
#endif

/*
** A single step of the Keccak mixing function for a 1600-bit state
*/
static void KeccakF1600Step(fsl_sha3_cx *p){
  int i;
  uint64_t B0, B1, B2, B3, B4;
  uint64_t C0, C1, C2, C3, C4;
  uint64_t D0, D1, D2, D3, D4;
  static const uint64_t RC[] = {
    0x0000000000000001ULL,  0x0000000000008082ULL,
    0x800000000000808aULL,  0x8000000080008000ULL,
    0x000000000000808bULL,  0x0000000080000001ULL,
    0x8000000080008081ULL,  0x8000000000008009ULL,
    0x000000000000008aULL,  0x0000000000000088ULL,
    0x0000000080008009ULL,  0x000000008000000aULL,
    0x000000008000808bULL,  0x800000000000008bULL,
    0x8000000000008089ULL,  0x8000000000008003ULL,
    0x8000000000008002ULL,  0x8000000000000080ULL,
    0x000000000000800aULL,  0x800000008000000aULL,
    0x8000000080008081ULL,  0x8000000000008080ULL,
    0x0000000080000001ULL,  0x8000000080008008ULL
  };
# define A00 (p->u.s[0])
# define A01 (p->u.s[1])
# define A02 (p->u.s[2])
# define A03 (p->u.s[3])
# define A04 (p->u.s[4])
# define A10 (p->u.s[5])
# define A11 (p->u.s[6])
# define A12 (p->u.s[7])
# define A13 (p->u.s[8])
# define A14 (p->u.s[9])
# define A20 (p->u.s[10])
# define A21 (p->u.s[11])
# define A22 (p->u.s[12])
# define A23 (p->u.s[13])
# define A24 (p->u.s[14])
# define A30 (p->u.s[15])
# define A31 (p->u.s[16])
# define A32 (p->u.s[17])
# define A33 (p->u.s[18])
# define A34 (p->u.s[19])
# define A40 (p->u.s[20])
# define A41 (p->u.s[21])
# define A42 (p->u.s[22])
# define A43 (p->u.s[23])
# define A44 (p->u.s[24])
# define ROL64(a,x) ((a<<x)|(a>>(64-x)))

  for(i=0; i<24; i+=4){
    C0 = A00^A10^A20^A30^A40;
    C1 = A01^A11^A21^A31^A41;
    C2 = A02^A12^A22^A32^A42;
    C3 = A03^A13^A23^A33^A43;
    C4 = A04^A14^A24^A34^A44;
    D0 = C4^ROL64(C1, 1);
    D1 = C0^ROL64(C2, 1);
    D2 = C1^ROL64(C3, 1);
    D3 = C2^ROL64(C4, 1);
    D4 = C3^ROL64(C0, 1);

    B0 = (A00^D0);
    B1 = ROL64((A11^D1), 44);
    B2 = ROL64((A22^D2), 43);
    B3 = ROL64((A33^D3), 21);
    B4 = ROL64((A44^D4), 14);
    A00 =   B0 ^((~B1)&  B2 );
    A00 ^= RC[i];
    A11 =   B1 ^((~B2)&  B3 );
    A22 =   B2 ^((~B3)&  B4 );
    A33 =   B3 ^((~B4)&  B0 );
    A44 =   B4 ^((~B0)&  B1 );

    B2 = ROL64((A20^D0), 3);
    B3 = ROL64((A31^D1), 45);
    B4 = ROL64((A42^D2), 61);
    B0 = ROL64((A03^D3), 28);
    B1 = ROL64((A14^D4), 20);
    A20 =   B0 ^((~B1)&  B2 );
    A31 =   B1 ^((~B2)&  B3 );
    A42 =   B2 ^((~B3)&  B4 );
    A03 =   B3 ^((~B4)&  B0 );
    A14 =   B4 ^((~B0)&  B1 );

    B4 = ROL64((A40^D0), 18);
    B0 = ROL64((A01^D1), 1);
    B1 = ROL64((A12^D2), 6);
    B2 = ROL64((A23^D3), 25);
    B3 = ROL64((A34^D4), 8);
    A40 =   B0 ^((~B1)&  B2 );
    A01 =   B1 ^((~B2)&  B3 );
    A12 =   B2 ^((~B3)&  B4 );
    A23 =   B3 ^((~B4)&  B0 );
    A34 =   B4 ^((~B0)&  B1 );

    B1 = ROL64((A10^D0), 36);
    B2 = ROL64((A21^D1), 10);
    B3 = ROL64((A32^D2), 15);
    B4 = ROL64((A43^D3), 56);
    B0 = ROL64((A04^D4), 27);
    A10 =   B0 ^((~B1)&  B2 );
    A21 =   B1 ^((~B2)&  B3 );
    A32 =   B2 ^((~B3)&  B4 );
    A43 =   B3 ^((~B4)&  B0 );
    A04 =   B4 ^((~B0)&  B1 );

    B3 = ROL64((A30^D0), 41);
    B4 = ROL64((A41^D1), 2);
    B0 = ROL64((A02^D2), 62);
    B1 = ROL64((A13^D3), 55);
    B2 = ROL64((A24^D4), 39);
    A30 =   B0 ^((~B1)&  B2 );
    A41 =   B1 ^((~B2)&  B3 );
    A02 =   B2 ^((~B3)&  B4 );
    A13 =   B3 ^((~B4)&  B0 );
    A24 =   B4 ^((~B0)&  B1 );

    C0 = A00^A20^A40^A10^A30;
    C1 = A11^A31^A01^A21^A41;
    C2 = A22^A42^A12^A32^A02;
    C3 = A33^A03^A23^A43^A13;
    C4 = A44^A14^A34^A04^A24;
    D0 = C4^ROL64(C1, 1);
    D1 = C0^ROL64(C2, 1);
    D2 = C1^ROL64(C3, 1);
    D3 = C2^ROL64(C4, 1);
    D4 = C3^ROL64(C0, 1);

    B0 = (A00^D0);
    B1 = ROL64((A31^D1), 44);
    B2 = ROL64((A12^D2), 43);
    B3 = ROL64((A43^D3), 21);
    B4 = ROL64((A24^D4), 14);
    A00 =   B0 ^((~B1)&  B2 );
    A00 ^= RC[i+1];
    A31 =   B1 ^((~B2)&  B3 );
    A12 =   B2 ^((~B3)&  B4 );
    A43 =   B3 ^((~B4)&  B0 );
    A24 =   B4 ^((~B0)&  B1 );

    B2 = ROL64((A40^D0), 3);
    B3 = ROL64((A21^D1), 45);
    B4 = ROL64((A02^D2), 61);
    B0 = ROL64((A33^D3), 28);
    B1 = ROL64((A14^D4), 20);
    A40 =   B0 ^((~B1)&  B2 );
    A21 =   B1 ^((~B2)&  B3 );
    A02 =   B2 ^((~B3)&  B4 );
    A33 =   B3 ^((~B4)&  B0 );
    A14 =   B4 ^((~B0)&  B1 );

    B4 = ROL64((A30^D0), 18);
    B0 = ROL64((A11^D1), 1);
    B1 = ROL64((A42^D2), 6);
    B2 = ROL64((A23^D3), 25);
    B3 = ROL64((A04^D4), 8);
    A30 =   B0 ^((~B1)&  B2 );
    A11 =   B1 ^((~B2)&  B3 );
    A42 =   B2 ^((~B3)&  B4 );
    A23 =   B3 ^((~B4)&  B0 );
    A04 =   B4 ^((~B0)&  B1 );

    B1 = ROL64((A20^D0), 36);
    B2 = ROL64((A01^D1), 10);
    B3 = ROL64((A32^D2), 15);
    B4 = ROL64((A13^D3), 56);
    B0 = ROL64((A44^D4), 27);
    A20 =   B0 ^((~B1)&  B2 );
    A01 =   B1 ^((~B2)&  B3 );
    A32 =   B2 ^((~B3)&  B4 );
    A13 =   B3 ^((~B4)&  B0 );
    A44 =   B4 ^((~B0)&  B1 );

    B3 = ROL64((A10^D0), 41);
    B4 = ROL64((A41^D1), 2);
    B0 = ROL64((A22^D2), 62);
    B1 = ROL64((A03^D3), 55);
    B2 = ROL64((A34^D4), 39);
    A10 =   B0 ^((~B1)&  B2 );
    A41 =   B1 ^((~B2)&  B3 );
    A22 =   B2 ^((~B3)&  B4 );
    A03 =   B3 ^((~B4)&  B0 );
    A34 =   B4 ^((~B0)&  B1 );

    C0 = A00^A40^A30^A20^A10;
    C1 = A31^A21^A11^A01^A41;
    C2 = A12^A02^A42^A32^A22;
    C3 = A43^A33^A23^A13^A03;
    C4 = A24^A14^A04^A44^A34;
    D0 = C4^ROL64(C1, 1);
    D1 = C0^ROL64(C2, 1);
    D2 = C1^ROL64(C3, 1);
    D3 = C2^ROL64(C4, 1);
    D4 = C3^ROL64(C0, 1);

    B0 = (A00^D0);
    B1 = ROL64((A21^D1), 44);
    B2 = ROL64((A42^D2), 43);
    B3 = ROL64((A13^D3), 21);
    B4 = ROL64((A34^D4), 14);
    A00 =   B0 ^((~B1)&  B2 );
    A00 ^= RC[i+2];
    A21 =   B1 ^((~B2)&  B3 );
    A42 =   B2 ^((~B3)&  B4 );
    A13 =   B3 ^((~B4)&  B0 );
    A34 =   B4 ^((~B0)&  B1 );

    B2 = ROL64((A30^D0), 3);
    B3 = ROL64((A01^D1), 45);
    B4 = ROL64((A22^D2), 61);
    B0 = ROL64((A43^D3), 28);
    B1 = ROL64((A14^D4), 20);
    A30 =   B0 ^((~B1)&  B2 );
    A01 =   B1 ^((~B2)&  B3 );
    A22 =   B2 ^((~B3)&  B4 );
    A43 =   B3 ^((~B4)&  B0 );
    A14 =   B4 ^((~B0)&  B1 );

    B4 = ROL64((A10^D0), 18);
    B0 = ROL64((A31^D1), 1);
    B1 = ROL64((A02^D2), 6);
    B2 = ROL64((A23^D3), 25);
    B3 = ROL64((A44^D4), 8);
    A10 =   B0 ^((~B1)&  B2 );
    A31 =   B1 ^((~B2)&  B3 );
    A02 =   B2 ^((~B3)&  B4 );
    A23 =   B3 ^((~B4)&  B0 );
    A44 =   B4 ^((~B0)&  B1 );

    B1 = ROL64((A40^D0), 36);
    B2 = ROL64((A11^D1), 10);
    B3 = ROL64((A32^D2), 15);
    B4 = ROL64((A03^D3), 56);
    B0 = ROL64((A24^D4), 27);
    A40 =   B0 ^((~B1)&  B2 );
    A11 =   B1 ^((~B2)&  B3 );
    A32 =   B2 ^((~B3)&  B4 );
    A03 =   B3 ^((~B4)&  B0 );
    A24 =   B4 ^((~B0)&  B1 );

    B3 = ROL64((A20^D0), 41);
    B4 = ROL64((A41^D1), 2);
    B0 = ROL64((A12^D2), 62);
    B1 = ROL64((A33^D3), 55);
    B2 = ROL64((A04^D4), 39);
    A20 =   B0 ^((~B1)&  B2 );
    A41 =   B1 ^((~B2)&  B3 );
    A12 =   B2 ^((~B3)&  B4 );
    A33 =   B3 ^((~B4)&  B0 );
    A04 =   B4 ^((~B0)&  B1 );

    C0 = A00^A30^A10^A40^A20;
    C1 = A21^A01^A31^A11^A41;
    C2 = A42^A22^A02^A32^A12;
    C3 = A13^A43^A23^A03^A33;
    C4 = A34^A14^A44^A24^A04;
    D0 = C4^ROL64(C1, 1);
    D1 = C0^ROL64(C2, 1);
    D2 = C1^ROL64(C3, 1);
    D3 = C2^ROL64(C4, 1);
    D4 = C3^ROL64(C0, 1);

    B0 = (A00^D0);
    B1 = ROL64((A01^D1), 44);
    B2 = ROL64((A02^D2), 43);
    B3 = ROL64((A03^D3), 21);
    B4 = ROL64((A04^D4), 14);
    A00 =   B0 ^((~B1)&  B2 );
    A00 ^= RC[i+3];
    A01 =   B1 ^((~B2)&  B3 );
    A02 =   B2 ^((~B3)&  B4 );
    A03 =   B3 ^((~B4)&  B0 );
    A04 =   B4 ^((~B0)&  B1 );

    B2 = ROL64((A10^D0), 3);
    B3 = ROL64((A11^D1), 45);
    B4 = ROL64((A12^D2), 61);
    B0 = ROL64((A13^D3), 28);
    B1 = ROL64((A14^D4), 20);
    A10 =   B0 ^((~B1)&  B2 );
    A11 =   B1 ^((~B2)&  B3 );
    A12 =   B2 ^((~B3)&  B4 );
    A13 =   B3 ^((~B4)&  B0 );
    A14 =   B4 ^((~B0)&  B1 );

    B4 = ROL64((A20^D0), 18);
    B0 = ROL64((A21^D1), 1);
    B1 = ROL64((A22^D2), 6);
    B2 = ROL64((A23^D3), 25);
    B3 = ROL64((A24^D4), 8);
    A20 =   B0 ^((~B1)&  B2 );
    A21 =   B1 ^((~B2)&  B3 );
    A22 =   B2 ^((~B3)&  B4 );
    A23 =   B3 ^((~B4)&  B0 );
    A24 =   B4 ^((~B0)&  B1 );

    B1 = ROL64((A30^D0), 36);
    B2 = ROL64((A31^D1), 10);
    B3 = ROL64((A32^D2), 15);
    B4 = ROL64((A33^D3), 56);
    B0 = ROL64((A34^D4), 27);
    A30 =   B0 ^((~B1)&  B2 );
    A31 =   B1 ^((~B2)&  B3 );
    A32 =   B2 ^((~B3)&  B4 );
    A33 =   B3 ^((~B4)&  B0 );
    A34 =   B4 ^((~B0)&  B1 );

    B3 = ROL64((A40^D0), 41);
    B4 = ROL64((A41^D1), 2);
    B0 = ROL64((A42^D2), 62);
    B1 = ROL64((A43^D3), 55);
    B2 = ROL64((A44^D4), 39);
    A40 =   B0 ^((~B1)&  B2 );
    A41 =   B1 ^((~B2)&  B3 );
    A42 =   B2 ^((~B3)&  B4 );
    A43 =   B3 ^((~B4)&  B0 );
    A44 =   B4 ^((~B0)&  B1 );
  }
# undef A00
# undef A01
# undef A02
# undef A03
# undef A04
# undef A10
# undef A11
# undef A12
# undef A13
# undef A14
# undef A20
# undef A21
# undef A22
# undef A23
# undef A24
# undef A30
# undef A31
# undef A32
# undef A33
# undef A34
# undef A40
# undef A41
# undef A42
# undef A43
# undef A44
# undef ROL64
}

enum fsl_sha3_hash_size fsl_sha3_hash_size_for_int(int n){
    switch(n){
      case 128: return FSL_SHA3_128; case 160: return FSL_SHA3_160;
      case 192: return FSL_SHA3_192; case 224: return FSL_SHA3_224;
      case 256: return FSL_SHA3_256; case 288: return FSL_SHA3_288;
      case 320: return FSL_SHA3_320; case 352: return FSL_SHA3_352;
      case 384: return FSL_SHA3_384; case 416: return FSL_SHA3_416;
      case 448: return FSL_SHA3_448; case 480: return FSL_SHA3_480;
      case 512: return FSL_SHA3_512;
      default: return FSL_SHA3_INVALID;
    }
}

void fsl_sha3_init(fsl_sha3_cx *cx){
  fsl_sha3_init2(cx, FSL_SHA3_DEFAULT);
}
void fsl_sha3_init2(fsl_sha3_cx *p, enum fsl_sha3_hash_size iSize){
  assert(iSize>0);
  memset(p, 0, sizeof(*p));
  p->size = iSize;
  if( iSize>=128 && iSize<=512 ){
    p->nRate = (1600 - ((iSize + 31)&~31)*2)/8;
  }else{
    p->nRate = (1600 - 2*256)/8;
  }
#if SHA3_BYTEORDER==1234
  /* Known to be little-endian at compile-time. No-op */
#elif SHA3_BYTEORDER==4321
  p->ixMask = 7;  /* Big-endian */
#else
  {
    static const unsigned int one = 1;
    if( 1==*(unsigned const char*)&one ){
      /* Little endian.  No byte swapping. */
      p->ixMask = 0;
    }else{
      /* Big endian.  Byte swap. */
      p->ixMask = 7;
    }
  }
#endif
}

void fsl_sha3_update( fsl_sha3_cx *p, void const *aData_, unsigned int nData ){
  unsigned char const * aData = (unsigned char const *)aData_;
  unsigned int i = 0;
#if SHA3_BYTEORDER==1234
  if( (p->nLoaded % 8)==0 && ((aData - (const unsigned char*)0)&7)==0 ){
    for(; i+7<nData; i+=8){
      p->u.s[p->nLoaded/8] ^= *(uint64_t*)&aData[i];
      p->nLoaded += 8;
      if( p->nLoaded>=p->nRate ){
        KeccakF1600Step(p);
        p->nLoaded = 0;
      }
    }
  }
#endif
  for(; i<nData; i++){
#if SHA3_BYTEORDER==1234
    p->u.x[p->nLoaded] ^= aData[i];
#elif SHA3_BYTEORDER==4321
    p->u.x[p->nLoaded^0x07] ^= aData[i];
#else
    p->u.x[p->nLoaded^p->ixMask] ^= aData[i];
#endif
    p->nLoaded++;
    if( p->nLoaded==p->nRate ){
      KeccakF1600Step(p);
      p->nLoaded = 0;
    }
  }
}

/*
** Convert a digest into base-16. must be at least nBytes long
** and zBuf must be at least nBytes*2 bytes long. This routine
** writes nBytes*2 hex-encoded bytes to zBuf, but does not write
** a terminating NUL byte.
*/
static void DigestToBase16(unsigned char *digest, unsigned char *zBuf, unsigned int nByte){
  static const unsigned char zEncode[] = "0123456789abcdef";
  unsigned int ix;
  for(ix=0; ix<nByte; ++ix){
    *zBuf++ = zEncode[(*digest>>4)&0xf];
    *zBuf++ = zEncode[*digest++ & 0xf];
  }
  /* *zBuf = '\0'; */
}

unsigned char const *fsl_sha3_end(fsl_sha3_cx *p){
  unsigned int i;
  if( p->nLoaded==p->nRate-1 ){
    const unsigned char c1 = 0x86;
    fsl_sha3_update(p, &c1, 1);
  }else{
    const unsigned char c2 = 0x06;
    const unsigned char c3 = 0x80;
    fsl_sha3_update(p, &c2, 1);
    p->nLoaded = p->nRate - 1;
    fsl_sha3_update(p, &c3, 1);
  }
  for(i=0; i<p->nRate; i++){
    p->u.x[i+p->nRate] = p->u.x[i^p->ixMask];
  }
  DigestToBase16( &p->u.x[p->nRate], p->hex, (int)p->size/8 );
  assert(0 == p->hex[(int)p->size/4+1]);
  return &p->u.x[p->nRate];
}

void fsl_sha3_digest_to_base16(unsigned char *digest, char *zBuf){
  static char const zEncode[] = "0123456789abcdef";
  int ix;
  for(ix=0; ix<FSL_STRLEN_K256/2; ix++){
    *zBuf++ = zEncode[(*digest>>4)&0xf];
    *zBuf++ = zEncode[*digest++ & 0xf];
  }
  *zBuf = '\0';
}


int fsl_sha3sum_stream(fsl_input_f src, void * srcState, fsl_buffer *pCksum){
  fsl_sha3_cx ctx;
  int rc;
  enum { BufSize = 1024 * 4 };
  unsigned char zBuf[BufSize];
  if(!src || !pCksum) return FSL_RC_MISUSE;
  fsl_sha3_init(&ctx);
  for(;;){
    fsl_size_t read = (fsl_size_t)BufSize;
    rc = src(srcState, zBuf, &read);
    if(rc) return rc;
    else if(read) fsl_sha3_update(&ctx, (unsigned char*)zBuf, read);
    if(read < (fsl_size_t)BufSize) break;
  }
  fsl_sha3_end(&ctx);
  fsl_buffer_reuse(pCksum);
  return fsl_buffer_append(pCksum, ctx.hex, fsl_strlen((const char *)ctx.hex));
}

int fsl_sha3sum_buffer(fsl_buffer const *pIn, fsl_buffer *pCksum){
  if(!pIn || !pCksum) return FSL_RC_MISUSE;
  else{
    fsl_sha3_cx ctx;
    int rc;
    fsl_sha3_init(&ctx);
    fsl_sha3_update(&ctx, pIn->mem, pIn->used);
    rc = fsl_buffer_reserve(pCksum, FSL_STRLEN_K256+1/*NUL*/);
    /*^^^^ DO NOT fsl_buffer_resize(), as pCksum is, more often than not,
      a cached reused buffer. */
    if(!rc){
      fsl_buffer_reuse(pCksum);
      fsl_sha3_end(&ctx);
      assert(fsl_strlen((char const*)ctx.hex)==FSL_STRLEN_K256);
      rc = fsl_buffer_append(pCksum, ctx.hex, fsl_strlen((char const*)ctx.hex));
      assert(!rc && "Cannot fail - pre-allocated");
      if(!rc){
        assert(FSL_STRLEN_K256==pCksum->used);
        assert(0==pCksum->mem[FSL_STRLEN_K256]);
      }
    }
    return rc;
  }
}

char *fsl_sha3sum_cstr(const char *zIn, fsl_int_t len){
  if(!zIn || !len) return NULL;
  else{
    fsl_sha3_cx ctx;
    fsl_sha3_init(&ctx);
    fsl_sha3_update(&ctx, zIn,
                    (len<0) ? fsl_strlen(zIn) : (fsl_size_t)len);
    fsl_sha3_end(&ctx);
    return fsl_strdup((char const *)ctx.hex);
  }
}

int fsl_sha3sum_filename(const char *zFilename, fsl_buffer *pCksum){
  if(!zFilename || !pCksum) return FSL_RC_MISUSE;
  else{
#if 1
    int rc;
    FILE *in = fsl_fopen(zFilename, "rb");
    if(!in) rc = FSL_RC_IO;
    else{
      rc = fsl_sha3sum_stream(fsl_input_f_FILE, in, pCksum);
      fsl_fclose(in);
    }
    return rc;
#else
    /* Requires v1 code which has not yet been ported in. */
    FILE *in;
    fsl_sha1_cx ctx;
    char zBuf[10240];
    int rc;

    if( fsl_wd_islink(zFilename) ){
      /* Instead of file content, return sha3 of link destination path */
      Blob destinationPath;
      
      blob_read_link(&destinationPath, zFilename);
      rc = fsl_sha3sum_buffer(&destinationPath, pCksum);
      fsl_buffer_clear(&destinationPath);
      return rc;
    }

    in = fossil_fopen(zFilename,"rb");
    if( in==0 ){
      return 1;
    }
    fsl_sha3_init(&ctx);
    for(;;){
      int n;
      n = fread(zBuf, 1, sizeof(zBuf), in);
      if( n<=0 ) break;
      fsl_sha3_update(&ctx, (unsigned char*)zBuf, (unsigned)n);
    }
    fclose_fclose(in);
    blob_zero(pCksum);
    blob_resize(pCksum, FSL_STRLEN_SHA3);
    fsl_sha3_end(&ctx);
    rc = fsl_buffer_append(pCksum, ctx.hex, fsl_strlen(ctx.hex));
    return rc;
#endif
  }
}


#undef SHA3_BYTEORDER
#undef MARKER
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Added src/sqlite3ext.h.















































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
/*
** 2006 June 7
**
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** This header file defines the SQLite interface for use by
** shared libraries that want to be imported as extensions into
** an SQLite instance.  Shared libraries that intend to be loaded
** as extensions by SQLite should #include this file instead of 
** sqlite3.h.
*/
#ifndef SQLITE3EXT_H
#define SQLITE3EXT_H
#include "sqlite3.h"

/*
** The following structure holds pointers to all of the SQLite API
** routines.
**
** WARNING:  In order to maintain backwards compatibility, add new
** interfaces to the end of this structure only.  If you insert new
** interfaces in the middle of this structure, then older different
** versions of SQLite will not be able to load each other's shared
** libraries!
*/
struct sqlite3_api_routines {
  void * (*aggregate_context)(sqlite3_context*,int nBytes);
  int  (*aggregate_count)(sqlite3_context*);
  int  (*bind_blob)(sqlite3_stmt*,int,const void*,int n,void(*)(void*));
  int  (*bind_double)(sqlite3_stmt*,int,double);
  int  (*bind_int)(sqlite3_stmt*,int,int);
  int  (*bind_int64)(sqlite3_stmt*,int,sqlite_int64);
  int  (*bind_null)(sqlite3_stmt*,int);
  int  (*bind_parameter_count)(sqlite3_stmt*);
  int  (*bind_parameter_index)(sqlite3_stmt*,const char*zName);
  const char * (*bind_parameter_name)(sqlite3_stmt*,int);
  int  (*bind_text)(sqlite3_stmt*,int,const char*,int n,void(*)(void*));
  int  (*bind_text16)(sqlite3_stmt*,int,const void*,int,void(*)(void*));
  int  (*bind_value)(sqlite3_stmt*,int,const sqlite3_value*);
  int  (*busy_handler)(sqlite3*,int(*)(void*,int),void*);
  int  (*busy_timeout)(sqlite3*,int ms);
  int  (*changes)(sqlite3*);
  int  (*close)(sqlite3*);
  int  (*collation_needed)(sqlite3*,void*,void(*)(void*,sqlite3*,
                           int eTextRep,const char*));
  int  (*collation_needed16)(sqlite3*,void*,void(*)(void*,sqlite3*,
                             int eTextRep,const void*));
  const void * (*column_blob)(sqlite3_stmt*,int iCol);
  int  (*column_bytes)(sqlite3_stmt*,int iCol);
  int  (*column_bytes16)(sqlite3_stmt*,int iCol);
  int  (*column_count)(sqlite3_stmt*pStmt);
  const char * (*column_database_name)(sqlite3_stmt*,int);
  const void * (*column_database_name16)(sqlite3_stmt*,int);
  const char * (*column_decltype)(sqlite3_stmt*,int i);
  const void * (*column_decltype16)(sqlite3_stmt*,int);
  double  (*column_double)(sqlite3_stmt*,int iCol);
  int  (*column_int)(sqlite3_stmt*,int iCol);
  sqlite_int64  (*column_int64)(sqlite3_stmt*,int iCol);
  const char * (*column_name)(sqlite3_stmt*,int);
  const void * (*column_name16)(sqlite3_stmt*,int);
  const char * (*column_origin_name)(sqlite3_stmt*,int);
  const void * (*column_origin_name16)(sqlite3_stmt*,int);
  const char * (*column_table_name)(sqlite3_stmt*,int);
  const void * (*column_table_name16)(sqlite3_stmt*,int);
  const unsigned char * (*column_text)(sqlite3_stmt*,int iCol);
  const void * (*column_text16)(sqlite3_stmt*,int iCol);
  int  (*column_type)(sqlite3_stmt*,int iCol);
  sqlite3_value* (*column_value)(sqlite3_stmt*,int iCol);
  void * (*commit_hook)(sqlite3*,int(*)(void*),void*);
  int  (*complete)(const char*sql);
  int  (*complete16)(const void*sql);
  int  (*create_collation)(sqlite3*,const char*,int,void*,
                           int(*)(void*,int,const void*,int,const void*));
  int  (*create_collation16)(sqlite3*,const void*,int,void*,
                             int(*)(void*,int,const void*,int,const void*));
  int  (*create_function)(sqlite3*,const char*,int,int,void*,
                          void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
                          void (*xStep)(sqlite3_context*,int,sqlite3_value**),
                          void (*xFinal)(sqlite3_context*));
  int  (*create_function16)(sqlite3*,const void*,int,int,void*,
                            void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
                            void (*xStep)(sqlite3_context*,int,sqlite3_value**),
                            void (*xFinal)(sqlite3_context*));
  int (*create_module)(sqlite3*,const char*,const sqlite3_module*,void*);
  int  (*data_count)(sqlite3_stmt*pStmt);
  sqlite3 * (*db_handle)(sqlite3_stmt*);
  int (*declare_vtab)(sqlite3*,const char*);
  int  (*enable_shared_cache)(int);
  int  (*errcode)(sqlite3*db);
  const char * (*errmsg)(sqlite3*);
  const void * (*errmsg16)(sqlite3*);
  int  (*exec)(sqlite3*,const char*,sqlite3_callback,void*,char**);
  int  (*expired)(sqlite3_stmt*);
  int  (*finalize)(sqlite3_stmt*pStmt);
  void  (*free)(void*);
  void  (*free_table)(char**result);
  int  (*get_autocommit)(sqlite3*);
  void * (*get_auxdata)(sqlite3_context*,int);
  int  (*get_table)(sqlite3*,const char*,char***,int*,int*,char**);
  int  (*global_recover)(void);
  void  (*interruptx)(sqlite3*);
  sqlite_int64  (*last_insert_rowid)(sqlite3*);
  const char * (*libversion)(void);
  int  (*libversion_number)(void);
  void *(*malloc)(int);
  char * (*mprintf)(const char*,...);
  int  (*open)(const char*,sqlite3**);
  int  (*open16)(const void*,sqlite3**);
  int  (*prepare)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
  int  (*prepare16)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
  void * (*profile)(sqlite3*,void(*)(void*,const char*,sqlite_uint64),void*);
  void  (*progress_handler)(sqlite3*,int,int(*)(void*),void*);
  void *(*realloc)(void*,int);
  int  (*reset)(sqlite3_stmt*pStmt);
  void  (*result_blob)(sqlite3_context*,const void*,int,void(*)(void*));
  void  (*result_double)(sqlite3_context*,double);
  void  (*result_error)(sqlite3_context*,const char*,int);
  void  (*result_error16)(sqlite3_context*,const void*,int);
  void  (*result_int)(sqlite3_context*,int);
  void  (*result_int64)(sqlite3_context*,sqlite_int64);
  void  (*result_null)(sqlite3_context*);
  void  (*result_text)(sqlite3_context*,const char*,int,void(*)(void*));
  void  (*result_text16)(sqlite3_context*,const void*,int,void(*)(void*));
  void  (*result_text16be)(sqlite3_context*,const void*,int,void(*)(void*));
  void  (*result_text16le)(sqlite3_context*,const void*,int,void(*)(void*));
  void  (*result_value)(sqlite3_context*,sqlite3_value*);
  void * (*rollback_hook)(sqlite3*,void(*)(void*),void*);
  int  (*set_authorizer)(sqlite3*,int(*)(void*,int,const char*,const char*,
                         const char*,const char*),void*);
  void  (*set_auxdata)(sqlite3_context*,int,void*,void (*)(void*));
  char * (*xsnprintf)(int,char*,const char*,...);
  int  (*step)(sqlite3_stmt*);
  int  (*table_column_metadata)(sqlite3*,const char*,const char*,const char*,
                                char const**,char const**,int*,int*,int*);
  void  (*thread_cleanup)(void);
  int  (*total_changes)(sqlite3*);
  void * (*trace)(sqlite3*,void(*xTrace)(void*,const char*),void*);
  int  (*transfer_bindings)(sqlite3_stmt*,sqlite3_stmt*);
  void * (*update_hook)(sqlite3*,void(*)(void*,int ,char const*,char const*,
                                         sqlite_int64),void*);
  void * (*user_data)(sqlite3_context*);
  const void * (*value_blob)(sqlite3_value*);
  int  (*value_bytes)(sqlite3_value*);
  int  (*value_bytes16)(sqlite3_value*);
  double  (*value_double)(sqlite3_value*);
  int  (*value_int)(sqlite3_value*);
  sqlite_int64  (*value_int64)(sqlite3_value*);
  int  (*value_numeric_type)(sqlite3_value*);
  const unsigned char * (*value_text)(sqlite3_value*);
  const void * (*value_text16)(sqlite3_value*);
  const void * (*value_text16be)(sqlite3_value*);
  const void * (*value_text16le)(sqlite3_value*);
  int  (*value_type)(sqlite3_value*);
  char *(*vmprintf)(const char*,va_list);
  /* Added ??? */
  int (*overload_function)(sqlite3*, const char *zFuncName, int nArg);
  /* Added by 3.3.13 */
  int (*prepare_v2)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
  int (*prepare16_v2)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
  int (*clear_bindings)(sqlite3_stmt*);
  /* Added by 3.4.1 */
  int (*create_module_v2)(sqlite3*,const char*,const sqlite3_module*,void*,
                          void (*xDestroy)(void *));
  /* Added by 3.5.0 */
  int (*bind_zeroblob)(sqlite3_stmt*,int,int);
  int (*blob_bytes)(sqlite3_blob*);
  int (*blob_close)(sqlite3_blob*);
  int (*blob_open)(sqlite3*,const char*,const char*,const char*,sqlite3_int64,
                   int,sqlite3_blob**);
  int (*blob_read)(sqlite3_blob*,void*,int,int);
  int (*blob_write)(sqlite3_blob*,const void*,int,int);
  int (*create_collation_v2)(sqlite3*,const char*,int,void*,
                             int(*)(void*,int,const void*,int,const void*),
                             void(*)(void*));
  int (*file_control)(sqlite3*,const char*,int,void*);
  sqlite3_int64 (*memory_highwater)(int);
  sqlite3_int64 (*memory_used)(void);
  sqlite3_mutex *(*mutex_alloc)(int);
  void (*mutex_enter)(sqlite3_mutex*);
  void (*mutex_free)(sqlite3_mutex*);
  void (*mutex_leave)(sqlite3_mutex*);
  int (*mutex_try)(sqlite3_mutex*);
  int (*open_v2)(const char*,sqlite3**,int,const char*);
  int (*release_memory)(int);
  void (*result_error_nomem)(sqlite3_context*);
  void (*result_error_toobig)(sqlite3_context*);
  int (*sleep)(int);
  void (*soft_heap_limit)(int);
  sqlite3_vfs *(*vfs_find)(const char*);
  int (*vfs_register)(sqlite3_vfs*,int);
  int (*vfs_unregister)(sqlite3_vfs*);
  int (*xthreadsafe)(void);
  void (*result_zeroblob)(sqlite3_context*,int);
  void (*result_error_code)(sqlite3_context*,int);
  int (*test_control)(int, ...);
  void (*randomness)(int,void*);
  sqlite3 *(*context_db_handle)(sqlite3_context*);
  int (*extended_result_codes)(sqlite3*,int);
  int (*limit)(sqlite3*,int,int);
  sqlite3_stmt *(*next_stmt)(sqlite3*,sqlite3_stmt*);
  const char *(*sql)(sqlite3_stmt*);
  int (*status)(int,int*,int*,int);
  int (*backup_finish)(sqlite3_backup*);
  sqlite3_backup *(*backup_init)(sqlite3*,const char*,sqlite3*,const char*);
  int (*backup_pagecount)(sqlite3_backup*);
  int (*backup_remaining)(sqlite3_backup*);
  int (*backup_step)(sqlite3_backup*,int);
  const char *(*compileoption_get)(int);
  int (*compileoption_used)(const char*);
  int (*create_function_v2)(sqlite3*,const char*,int,int,void*,
                            void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
                            void (*xStep)(sqlite3_context*,int,sqlite3_value**),
                            void (*xFinal)(sqlite3_context*),
                            void(*xDestroy)(void*));
  int (*db_config)(sqlite3*,int,...);
  sqlite3_mutex *(*db_mutex)(sqlite3*);
  int (*db_status)(sqlite3*,int,int*,int*,int);
  int (*extended_errcode)(sqlite3*);
  void (*log)(int,const char*,...);
  sqlite3_int64 (*soft_heap_limit64)(sqlite3_int64);
  const char *(*sourceid)(void);
  int (*stmt_status)(sqlite3_stmt*,int,int);
  int (*strnicmp)(const char*,const char*,int);
  int (*unlock_notify)(sqlite3*,void(*)(void**,int),void*);
  int (*wal_autocheckpoint)(sqlite3*,int);
  int (*wal_checkpoint)(sqlite3*,const char*);
  void *(*wal_hook)(sqlite3*,int(*)(void*,sqlite3*,const char*,int),void*);
  int (*blob_reopen)(sqlite3_blob*,sqlite3_int64);
  int (*vtab_config)(sqlite3*,int op,...);
  int (*vtab_on_conflict)(sqlite3*);
  /* Version 3.7.16 and later */
  int (*close_v2)(sqlite3*);
  const char *(*db_filename)(sqlite3*,const char*);
  int (*db_readonly)(sqlite3*,const char*);
  int (*db_release_memory)(sqlite3*);
  const char *(*errstr)(int);
  int (*stmt_busy)(sqlite3_stmt*);
  int (*stmt_readonly)(sqlite3_stmt*);
  int (*stricmp)(const char*,const char*);
  int (*uri_boolean)(const char*,const char*,int);
  sqlite3_int64 (*uri_int64)(const char*,const char*,sqlite3_int64);
  const char *(*uri_parameter)(const char*,const char*);
  char *(*xvsnprintf)(int,char*,const char*,va_list);
  int (*wal_checkpoint_v2)(sqlite3*,const char*,int,int*,int*);
  /* Version 3.8.7 and later */
  int (*auto_extension)(void(*)(void));
  int (*bind_blob64)(sqlite3_stmt*,int,const void*,sqlite3_uint64,
                     void(*)(void*));
  int (*bind_text64)(sqlite3_stmt*,int,const char*,sqlite3_uint64,
                      void(*)(void*),unsigned char);
  int (*cancel_auto_extension)(void(*)(void));
  int (*load_extension)(sqlite3*,const char*,const char*,char**);
  void *(*malloc64)(sqlite3_uint64);
  sqlite3_uint64 (*msize)(void*);
  void *(*realloc64)(void*,sqlite3_uint64);
  void (*reset_auto_extension)(void);
  void (*result_blob64)(sqlite3_context*,const void*,sqlite3_uint64,
                        void(*)(void*));
  void (*result_text64)(sqlite3_context*,const char*,sqlite3_uint64,
                         void(*)(void*), unsigned char);
  int (*strglob)(const char*,const char*);
  /* Version 3.8.11 and later */
  sqlite3_value *(*value_dup)(const sqlite3_value*);
  void (*value_free)(sqlite3_value*);
  int (*result_zeroblob64)(sqlite3_context*,sqlite3_uint64);
  int (*bind_zeroblob64)(sqlite3_stmt*, int, sqlite3_uint64);
  /* Version 3.9.0 and later */
  unsigned int (*value_subtype)(sqlite3_value*);
  void (*result_subtype)(sqlite3_context*,unsigned int);
  /* Version 3.10.0 and later */
  int (*status64)(int,sqlite3_int64*,sqlite3_int64*,int);
  int (*strlike)(const char*,const char*,unsigned int);
  int (*db_cacheflush)(sqlite3*);
  /* Version 3.12.0 and later */
  int (*system_errno)(sqlite3*);
  /* Version 3.14.0 and later */
  int (*trace_v2)(sqlite3*,unsigned,int(*)(unsigned,void*,void*,void*),void*);
  char *(*expanded_sql)(sqlite3_stmt*);
  /* Version 3.18.0 and later */
  void (*set_last_insert_rowid)(sqlite3*,sqlite3_int64);
  /* Version 3.20.0 and later */
  int (*prepare_v3)(sqlite3*,const char*,int,unsigned int,
                    sqlite3_stmt**,const char**);
  int (*prepare16_v3)(sqlite3*,const void*,int,unsigned int,
                      sqlite3_stmt**,const void**);
  int (*bind_pointer)(sqlite3_stmt*,int,void*,const char*,void(*)(void*));
  void (*result_pointer)(sqlite3_context*,void*,const char*,void(*)(void*));
  void *(*value_pointer)(sqlite3_value*,const char*);
  int (*vtab_nochange)(sqlite3_context*);
  int (*value_nochange)(sqlite3_value*);
  const char *(*vtab_collation)(sqlite3_index_info*,int);
  /* Version 3.24.0 and later */
  int (*keyword_count)(void);
  int (*keyword_name)(int,const char**,int*);
  int (*keyword_check)(const char*,int);
  sqlite3_str *(*str_new)(sqlite3*);
  char *(*str_finish)(sqlite3_str*);
  void (*str_appendf)(sqlite3_str*, const char *zFormat, ...);
  void (*str_vappendf)(sqlite3_str*, const char *zFormat, va_list);
  void (*str_append)(sqlite3_str*, const char *zIn, int N);
  void (*str_appendall)(sqlite3_str*, const char *zIn);
  void (*str_appendchar)(sqlite3_str*, int N, char C);
  void (*str_reset)(sqlite3_str*);
  int (*str_errcode)(sqlite3_str*);
  int (*str_length)(sqlite3_str*);
  char *(*str_value)(sqlite3_str*);
  /* Version 3.25.0 and later */
  int (*create_window_function)(sqlite3*,const char*,int,int,void*,
                            void (*xStep)(sqlite3_context*,int,sqlite3_value**),
                            void (*xFinal)(sqlite3_context*),
                            void (*xValue)(sqlite3_context*),
                            void (*xInv)(sqlite3_context*,int,sqlite3_value**),
                            void(*xDestroy)(void*));
  /* Version 3.26.0 and later */
  const char *(*normalized_sql)(sqlite3_stmt*);
  /* Version 3.28.0 and later */
  int (*stmt_isexplain)(sqlite3_stmt*);
  int (*value_frombind)(sqlite3_value*);
  /* Version 3.30.0 and later */
  int (*drop_modules)(sqlite3*,const char**);
  /* Version 3.31.0 and later */
  sqlite3_int64 (*hard_heap_limit64)(sqlite3_int64);
  const char *(*uri_key)(const char*,int);
  const char *(*filename_database)(const char*);
  const char *(*filename_journal)(const char*);
  const char *(*filename_wal)(const char*);
  /* Version 3.32.0 and later */
  char *(*create_filename)(const char*,const char*,const char*,
                           int,const char**);
  void (*free_filename)(char*);
  sqlite3_file *(*database_file_object)(const char*);
  /* Version 3.34.0 and later */
  int (*txn_state)(sqlite3*,const char*);
};

/*
** This is the function signature used for all extension entry points.  It
** is also defined in the file "loadext.c".
*/
typedef int (*sqlite3_loadext_entry)(
  sqlite3 *db,                       /* Handle to the database. */
  char **pzErrMsg,                   /* Used to set error string on failure. */
  const sqlite3_api_routines *pThunk /* Extension API function pointers. */
);

/*
** The following macros redefine the API routines so that they are
** redirected through the global sqlite3_api structure.
**
** This header file is also used by the loadext.c source file
** (part of the main SQLite library - not an extension) so that
** it can get access to the sqlite3_api_routines structure
** definition.  But the main library does not want to redefine
** the API.  So the redefinition macros are only valid if the
** SQLITE_CORE macros is undefined.
*/
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
#define sqlite3_aggregate_context      sqlite3_api->aggregate_context
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_aggregate_count        sqlite3_api->aggregate_count
#endif
#define sqlite3_bind_blob              sqlite3_api->bind_blob
#define sqlite3_bind_double            sqlite3_api->bind_double
#define sqlite3_bind_int               sqlite3_api->bind_int
#define sqlite3_bind_int64             sqlite3_api->bind_int64
#define sqlite3_bind_null              sqlite3_api->bind_null
#define sqlite3_bind_parameter_count   sqlite3_api->bind_parameter_count
#define sqlite3_bind_parameter_index   sqlite3_api->bind_parameter_index
#define sqlite3_bind_parameter_name    sqlite3_api->bind_parameter_name
#define sqlite3_bind_text              sqlite3_api->bind_text
#define sqlite3_bind_text16            sqlite3_api->bind_text16
#define sqlite3_bind_value             sqlite3_api->bind_value
#define sqlite3_busy_handler           sqlite3_api->busy_handler
#define sqlite3_busy_timeout           sqlite3_api->busy_timeout
#define sqlite3_changes                sqlite3_api->changes
#define sqlite3_close                  sqlite3_api->close
#define sqlite3_collation_needed       sqlite3_api->collation_needed
#define sqlite3_collation_needed16     sqlite3_api->collation_needed16
#define sqlite3_column_blob            sqlite3_api->column_blob
#define sqlite3_column_bytes           sqlite3_api->column_bytes
#define sqlite3_column_bytes16         sqlite3_api->column_bytes16
#define sqlite3_column_count           sqlite3_api->column_count
#define sqlite3_column_database_name   sqlite3_api->column_database_name
#define sqlite3_column_database_name16 sqlite3_api->column_database_name16
#define sqlite3_column_decltype        sqlite3_api->column_decltype
#define sqlite3_column_decltype16      sqlite3_api->column_decltype16
#define sqlite3_column_double          sqlite3_api->column_double
#define sqlite3_column_int             sqlite3_api->column_int
#define sqlite3_column_int64           sqlite3_api->column_int64
#define sqlite3_column_name            sqlite3_api->column_name
#define sqlite3_column_name16          sqlite3_api->column_name16
#define sqlite3_column_origin_name     sqlite3_api->column_origin_name
#define sqlite3_column_origin_name16   sqlite3_api->column_origin_name16
#define sqlite3_column_table_name      sqlite3_api->column_table_name
#define sqlite3_column_table_name16    sqlite3_api->column_table_name16
#define sqlite3_column_text            sqlite3_api->column_text
#define sqlite3_column_text16          sqlite3_api->column_text16
#define sqlite3_column_type            sqlite3_api->column_type
#define sqlite3_column_value           sqlite3_api->column_value
#define sqlite3_commit_hook            sqlite3_api->commit_hook
#define sqlite3_complete               sqlite3_api->complete
#define sqlite3_complete16             sqlite3_api->complete16
#define sqlite3_create_collation       sqlite3_api->create_collation
#define sqlite3_create_collation16     sqlite3_api->create_collation16
#define sqlite3_create_function        sqlite3_api->create_function
#define sqlite3_create_function16      sqlite3_api->create_function16
#define sqlite3_create_module          sqlite3_api->create_module
#define sqlite3_create_module_v2       sqlite3_api->create_module_v2
#define sqlite3_data_count             sqlite3_api->data_count
#define sqlite3_db_handle              sqlite3_api->db_handle
#define sqlite3_declare_vtab           sqlite3_api->declare_vtab
#define sqlite3_enable_shared_cache    sqlite3_api->enable_shared_cache
#define sqlite3_errcode                sqlite3_api->errcode
#define sqlite3_errmsg                 sqlite3_api->errmsg
#define sqlite3_errmsg16               sqlite3_api->errmsg16
#define sqlite3_exec                   sqlite3_api->exec
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_expired                sqlite3_api->expired
#endif
#define sqlite3_finalize               sqlite3_api->finalize
#define sqlite3_free                   sqlite3_api->free
#define sqlite3_free_table             sqlite3_api->free_table
#define sqlite3_get_autocommit         sqlite3_api->get_autocommit
#define sqlite3_get_auxdata            sqlite3_api->get_auxdata
#define sqlite3_get_table              sqlite3_api->get_table
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_global_recover         sqlite3_api->global_recover
#endif
#define sqlite3_interrupt              sqlite3_api->interruptx
#define sqlite3_last_insert_rowid      sqlite3_api->last_insert_rowid
#define sqlite3_libversion             sqlite3_api->libversion
#define sqlite3_libversion_number      sqlite3_api->libversion_number
#define sqlite3_malloc                 sqlite3_api->malloc
#define sqlite3_mprintf                sqlite3_api->mprintf
#define sqlite3_open                   sqlite3_api->open
#define sqlite3_open16                 sqlite3_api->open16
#define sqlite3_prepare                sqlite3_api->prepare
#define sqlite3_prepare16              sqlite3_api->prepare16
#define sqlite3_prepare_v2             sqlite3_api->prepare_v2
#define sqlite3_prepare16_v2           sqlite3_api->prepare16_v2
#define sqlite3_profile                sqlite3_api->profile
#define sqlite3_progress_handler       sqlite3_api->progress_handler
#define sqlite3_realloc                sqlite3_api->realloc
#define sqlite3_reset                  sqlite3_api->reset
#define sqlite3_result_blob            sqlite3_api->result_blob
#define sqlite3_result_double          sqlite3_api->result_double
#define sqlite3_result_error           sqlite3_api->result_error
#define sqlite3_result_error16         sqlite3_api->result_error16
#define sqlite3_result_int             sqlite3_api->result_int
#define sqlite3_result_int64           sqlite3_api->result_int64
#define sqlite3_result_null            sqlite3_api->result_null
#define sqlite3_result_text            sqlite3_api->result_text
#define sqlite3_result_text16          sqlite3_api->result_text16
#define sqlite3_result_text16be        sqlite3_api->result_text16be
#define sqlite3_result_text16le        sqlite3_api->result_text16le
#define sqlite3_result_value           sqlite3_api->result_value
#define sqlite3_rollback_hook          sqlite3_api->rollback_hook
#define sqlite3_set_authorizer         sqlite3_api->set_authorizer
#define sqlite3_set_auxdata            sqlite3_api->set_auxdata
#define sqlite3_snprintf               sqlite3_api->xsnprintf
#define sqlite3_step                   sqlite3_api->step
#define sqlite3_table_column_metadata  sqlite3_api->table_column_metadata
#define sqlite3_thread_cleanup         sqlite3_api->thread_cleanup
#define sqlite3_total_changes          sqlite3_api->total_changes
#define sqlite3_trace                  sqlite3_api->trace
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_transfer_bindings      sqlite3_api->transfer_bindings
#endif
#define sqlite3_update_hook            sqlite3_api->update_hook
#define sqlite3_user_data              sqlite3_api->user_data
#define sqlite3_value_blob             sqlite3_api->value_blob
#define sqlite3_value_bytes            sqlite3_api->value_bytes
#define sqlite3_value_bytes16          sqlite3_api->value_bytes16
#define sqlite3_value_double           sqlite3_api->value_double
#define sqlite3_value_int              sqlite3_api->value_int
#define sqlite3_value_int64            sqlite3_api->value_int64
#define sqlite3_value_numeric_type     sqlite3_api->value_numeric_type
#define sqlite3_value_text             sqlite3_api->value_text
#define sqlite3_value_text16           sqlite3_api->value_text16
#define sqlite3_value_text16be         sqlite3_api->value_text16be
#define sqlite3_value_text16le         sqlite3_api->value_text16le
#define sqlite3_value_type             sqlite3_api->value_type
#define sqlite3_vmprintf               sqlite3_api->vmprintf
#define sqlite3_vsnprintf              sqlite3_api->xvsnprintf
#define sqlite3_overload_function      sqlite3_api->overload_function
#define sqlite3_prepare_v2             sqlite3_api->prepare_v2
#define sqlite3_prepare16_v2           sqlite3_api->prepare16_v2
#define sqlite3_clear_bindings         sqlite3_api->clear_bindings
#define sqlite3_bind_zeroblob          sqlite3_api->bind_zeroblob
#define sqlite3_blob_bytes             sqlite3_api->blob_bytes
#define sqlite3_blob_close             sqlite3_api->blob_close
#define sqlite3_blob_open              sqlite3_api->blob_open
#define sqlite3_blob_read              sqlite3_api->blob_read
#define sqlite3_blob_write             sqlite3_api->blob_write
#define sqlite3_create_collation_v2    sqlite3_api->create_collation_v2
#define sqlite3_file_control           sqlite3_api->file_control
#define sqlite3_memory_highwater       sqlite3_api->memory_highwater
#define sqlite3_memory_used            sqlite3_api->memory_used
#define sqlite3_mutex_alloc            sqlite3_api->mutex_alloc
#define sqlite3_mutex_enter            sqlite3_api->mutex_enter
#define sqlite3_mutex_free             sqlite3_api->mutex_free
#define sqlite3_mutex_leave            sqlite3_api->mutex_leave
#define sqlite3_mutex_try              sqlite3_api->mutex_try
#define sqlite3_open_v2                sqlite3_api->open_v2
#define sqlite3_release_memory         sqlite3_api->release_memory
#define sqlite3_result_error_nomem     sqlite3_api->result_error_nomem
#define sqlite3_result_error_toobig    sqlite3_api->result_error_toobig
#define sqlite3_sleep                  sqlite3_api->sleep
#define sqlite3_soft_heap_limit        sqlite3_api->soft_heap_limit
#define sqlite3_vfs_find               sqlite3_api->vfs_find
#define sqlite3_vfs_register           sqlite3_api->vfs_register
#define sqlite3_vfs_unregister         sqlite3_api->vfs_unregister
#define sqlite3_threadsafe             sqlite3_api->xthreadsafe
#define sqlite3_result_zeroblob        sqlite3_api->result_zeroblob
#define sqlite3_result_error_code      sqlite3_api->result_error_code
#define sqlite3_test_control           sqlite3_api->test_control
#define sqlite3_randomness             sqlite3_api->randomness
#define sqlite3_context_db_handle      sqlite3_api->context_db_handle
#define sqlite3_extended_result_codes  sqlite3_api->extended_result_codes
#define sqlite3_limit                  sqlite3_api->limit
#define sqlite3_next_stmt              sqlite3_api->next_stmt
#define sqlite3_sql                    sqlite3_api->sql
#define sqlite3_status                 sqlite3_api->status
#define sqlite3_backup_finish          sqlite3_api->backup_finish
#define sqlite3_backup_init            sqlite3_api->backup_init
#define sqlite3_backup_pagecount       sqlite3_api->backup_pagecount
#define sqlite3_backup_remaining       sqlite3_api->backup_remaining
#define sqlite3_backup_step            sqlite3_api->backup_step
#define sqlite3_compileoption_get      sqlite3_api->compileoption_get
#define sqlite3_compileoption_used     sqlite3_api->compileoption_used
#define sqlite3_create_function_v2     sqlite3_api->create_function_v2
#define sqlite3_db_config              sqlite3_api->db_config
#define sqlite3_db_mutex               sqlite3_api->db_mutex
#define sqlite3_db_status              sqlite3_api->db_status
#define sqlite3_extended_errcode       sqlite3_api->extended_errcode
#define sqlite3_log                    sqlite3_api->log
#define sqlite3_soft_heap_limit64      sqlite3_api->soft_heap_limit64
#define sqlite3_sourceid               sqlite3_api->sourceid
#define sqlite3_stmt_status            sqlite3_api->stmt_status
#define sqlite3_strnicmp               sqlite3_api->strnicmp
#define sqlite3_unlock_notify          sqlite3_api->unlock_notify
#define sqlite3_wal_autocheckpoint     sqlite3_api->wal_autocheckpoint
#define sqlite3_wal_checkpoint         sqlite3_api->wal_checkpoint
#define sqlite3_wal_hook               sqlite3_api->wal_hook
#define sqlite3_blob_reopen            sqlite3_api->blob_reopen
#define sqlite3_vtab_config            sqlite3_api->vtab_config
#define sqlite3_vtab_on_conflict       sqlite3_api->vtab_on_conflict
/* Version 3.7.16 and later */
#define sqlite3_close_v2               sqlite3_api->close_v2
#define sqlite3_db_filename            sqlite3_api->db_filename
#define sqlite3_db_readonly            sqlite3_api->db_readonly
#define sqlite3_db_release_memory      sqlite3_api->db_release_memory
#define sqlite3_errstr                 sqlite3_api->errstr
#define sqlite3_stmt_busy              sqlite3_api->stmt_busy
#define sqlite3_stmt_readonly          sqlite3_api->stmt_readonly
#define sqlite3_stricmp                sqlite3_api->stricmp
#define sqlite3_uri_boolean            sqlite3_api->uri_boolean
#define sqlite3_uri_int64              sqlite3_api->uri_int64
#define sqlite3_uri_parameter          sqlite3_api->uri_parameter
#define sqlite3_uri_vsnprintf          sqlite3_api->xvsnprintf
#define sqlite3_wal_checkpoint_v2      sqlite3_api->wal_checkpoint_v2
/* Version 3.8.7 and later */
#define sqlite3_auto_extension         sqlite3_api->auto_extension
#define sqlite3_bind_blob64            sqlite3_api->bind_blob64
#define sqlite3_bind_text64            sqlite3_api->bind_text64
#define sqlite3_cancel_auto_extension  sqlite3_api->cancel_auto_extension
#define sqlite3_load_extension         sqlite3_api->load_extension
#define sqlite3_malloc64               sqlite3_api->malloc64
#define sqlite3_msize                  sqlite3_api->msize
#define sqlite3_realloc64              sqlite3_api->realloc64
#define sqlite3_reset_auto_extension   sqlite3_api->reset_auto_extension
#define sqlite3_result_blob64          sqlite3_api->result_blob64
#define sqlite3_result_text64          sqlite3_api->result_text64
#define sqlite3_strglob                sqlite3_api->strglob
/* Version 3.8.11 and later */
#define sqlite3_value_dup              sqlite3_api->value_dup
#define sqlite3_value_free             sqlite3_api->value_free
#define sqlite3_result_zeroblob64      sqlite3_api->result_zeroblob64
#define sqlite3_bind_zeroblob64        sqlite3_api->bind_zeroblob64
/* Version 3.9.0 and later */
#define sqlite3_value_subtype          sqlite3_api->value_subtype
#define sqlite3_result_subtype         sqlite3_api->result_subtype
/* Version 3.10.0 and later */
#define sqlite3_status64               sqlite3_api->status64
#define sqlite3_strlike                sqlite3_api->strlike
#define sqlite3_db_cacheflush          sqlite3_api->db_cacheflush
/* Version 3.12.0 and later */
#define sqlite3_system_errno           sqlite3_api->system_errno
/* Version 3.14.0 and later */
#define sqlite3_trace_v2               sqlite3_api->trace_v2
#define sqlite3_expanded_sql           sqlite3_api->expanded_sql
/* Version 3.18.0 and later */
#define sqlite3_set_last_insert_rowid  sqlite3_api->set_last_insert_rowid
/* Version 3.20.0 and later */
#define sqlite3_prepare_v3             sqlite3_api->prepare_v3
#define sqlite3_prepare16_v3           sqlite3_api->prepare16_v3
#define sqlite3_bind_pointer           sqlite3_api->bind_pointer
#define sqlite3_result_pointer         sqlite3_api->result_pointer
#define sqlite3_value_pointer          sqlite3_api->value_pointer
/* Version 3.22.0 and later */
#define sqlite3_vtab_nochange          sqlite3_api->vtab_nochange
#define sqlite3_value_nochange         sqlite3_api->value_nochange
#define sqlite3_vtab_collation         sqlite3_api->vtab_collation
/* Version 3.24.0 and later */
#define sqlite3_keyword_count          sqlite3_api->keyword_count
#define sqlite3_keyword_name           sqlite3_api->keyword_name
#define sqlite3_keyword_check          sqlite3_api->keyword_check
#define sqlite3_str_new                sqlite3_api->str_new
#define sqlite3_str_finish             sqlite3_api->str_finish
#define sqlite3_str_appendf            sqlite3_api->str_appendf
#define sqlite3_str_vappendf           sqlite3_api->str_vappendf
#define sqlite3_str_append             sqlite3_api->str_append
#define sqlite3_str_appendall          sqlite3_api->str_appendall
#define sqlite3_str_appendchar         sqlite3_api->str_appendchar
#define sqlite3_str_reset              sqlite3_api->str_reset
#define sqlite3_str_errcode            sqlite3_api->str_errcode
#define sqlite3_str_length             sqlite3_api->str_length
#define sqlite3_str_value              sqlite3_api->str_value
/* Version 3.25.0 and later */
#define sqlite3_create_window_function sqlite3_api->create_window_function
/* Version 3.26.0 and later */
#define sqlite3_normalized_sql         sqlite3_api->normalized_sql
/* Version 3.28.0 and later */
#define sqlite3_stmt_isexplain         sqlite3_api->stmt_isexplain
#define sqlite3_value_frombind         sqlite3_api->value_frombind
/* Version 3.30.0 and later */
#define sqlite3_drop_modules           sqlite3_api->drop_modules
/* Version 3.31.0 and later */
#define sqlite3_hard_heap_limit64      sqlite3_api->hard_heap_limit64
#define sqlite3_uri_key                sqlite3_api->uri_key
#define sqlite3_filename_database      sqlite3_api->filename_database
#define sqlite3_filename_journal       sqlite3_api->filename_journal
#define sqlite3_filename_wal           sqlite3_api->filename_wal
/* Version 3.32.0 and later */
#define sqlite3_create_filename        sqlite3_api->create_filename
#define sqlite3_free_filename          sqlite3_api->free_filename
#define sqlite3_database_file_object   sqlite3_api->database_file_object
/* Version 3.34.0 and later */
#define sqlite3_txn_state              sqlite3_api->txn_state
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */

#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
  /* This case when the file really is being compiled as a loadable 
  ** extension */
# define SQLITE_EXTENSION_INIT1     const sqlite3_api_routines *sqlite3_api=0;
# define SQLITE_EXTENSION_INIT2(v)  sqlite3_api=v;
# define SQLITE_EXTENSION_INIT3     \
    extern const sqlite3_api_routines *sqlite3_api;
#else
  /* This case when the file is being statically linked into the 
  ** application */
# define SQLITE_EXTENSION_INIT1     /*no-op*/
# define SQLITE_EXTENSION_INIT2(v)  (void)v; /* unused parameter */
# define SQLITE_EXTENSION_INIT3     /*no-op*/
#endif

#endif /* SQLITE3EXT_H */

Deleted src/strftime.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
/*******************************************************************************
 *  The Elm Mail System  -  $Revision: 1.3 $   $State: Exp $
 *
 * Public-domain relatively quick-and-dirty implemenation of
 * ANSI library routine for System V Unix systems.
 *
 * Arnold Robbins
 *
   *****************************************************************************
 * Bug reports, patches, comments, suggestions should be sent to:
 * (Note: this routine is provided as is, without support for those sites that
 *    do not have strftime in their library)
 *
 *    Syd Weinstein, Elm Coordinator
 *    elm@DSI.COM            dsinc!elm
 *
   *****************************************************************************
 * $Log: strftime.c,v $
 * Revision 1.3  1993/10/09  19:38:51  smace
 * Update to elm 2.4 pl23 release version
 *
 * Revision 5.8  1993/08/23  02:46:51  syd
 * Test ANSI_C, not __STDC__ (which is not set on e.g. AIX).
 * From: decwrl!uunet.UU.NET!fin!chip (Chip Salzenberg)
 *
 * Revision 5.7  1993/08/03  19:28:39  syd
 * Elm tries to replace the system toupper() and tolower() on current
 * BSD systems, which is unnecessary.  Even worse, the replacements
 * collide during linking with routines in isctype.o.  This patch adds
 * a Configure test to determine whether replacements are really needed
 * (BROKE_CTYPE definition).  The <ctype.h> header file is now included
 * globally through hdrs/defs.h and the BROKE_CTYPE patchup is handled
 * there.  Inclusion of <ctype.h> was removed from *all* the individual
 * files, and the toupper() and tolower() routines in lib/opt_utils.c
 * were dropped.
 * From: chip@chinacat.unicom.com (Chip Rosenthal)
 *
 * Revision 5.6  1993/08/03  19:20:31  syd
 * Implement new timezone handling.  New file lib/get_tz.c with new timezone
 * routines.  Added new TZMINS_USE_xxxxxx and TZNAME_USE_xxxxxx configuration
 * definitions.  Obsoleted TZNAME, ALTCHECK, and TZ_MINUTESWEST configuration
 * definitions.  Updated Configure.  Modified lib/getarpdate.c and
 * lib/strftime.c to use new timezone routines.
 * From: chip@chinacat.unicom.com (Chip Rosenthal)
 *
 * Revision 5.5  1993/06/10  03:17:45  syd
 * Change from TZNAME_MISSING to TZNAME
 * From: Syd via request from Dan Blanchard
 *
 * Revision 5.4  1993/05/08  19:56:45  syd
 * update to newer version
 * From: Syd
 *
 * Revision 5.3  1993/04/21  01:42:23  syd
 * avoid name conflicts on min and max
 *
 * Revision 5.2  1993/04/16  04:29:34  syd
 * attempt to bsdize a bit strftime
 * From: many via syd
 *
 * Revision 5.1  1993/01/27  18:52:15  syd
 * Initial checkin of contributed public domain routine.
 * This routine is provided as is and not covered by Elm Copyright.
   ****************************************************************************/

/*
 * strftime.c
 *
 * Public-domain relatively quick-and-dirty implementation of
 * ANSI library routine for System V Unix systems.
 *
 * It's written in old-style C for maximal portability.
 * However, since I'm used to prototypes, I've included them too.
 *
 * If you want stuff in the System V ascftime routine, add the SYSV_EXT define.
 * For extensions from SunOS, add SUNOS_EXT.
 * For stuff needed to implement the P1003.2 date command, add POSIX2_DATE.
 * For complete POSIX semantics, add POSIX_SEMANTICS.
 *
 * The code for %c, %x, and %X is my best guess as to what's "appropriate".
 * This version ignores LOCALE information.
 * It also doesn't worry about multi-byte characters.
 * So there.
 *
 * This file is also shipped with GAWK (GNU Awk), gawk specific bits of
 * code are included if GAWK is defined.
 *
 * Arnold Robbins
 * January, February, March, 1991
 * Updated March, April 1992
 * Updated May, 1993
 *
 * Fixes from ado@elsie.nci.nih.gov
 * February 1991, May 1992
 * Fixes from Tor Lillqvist tor@tik.vtt.fi
 * May, 1993
 *
 * Fast-forward to July 2013...
 *
 * stephan@wanderinghorse.net copied these sources from:
 *
 * ftp://ftp-archive.freebsd.org/pub/FreeBSD-Archive/old-releases/i386/1.0-RELEASE/ports/elm/lib/strftime.c
 *
 * And made the following changes:
 *
 * Removed ancient non-ANSI decls. Added some headers to get it to
 * compile for me. Renamed functions to fit into my project. Replaced
 * hard tabs with 4 spaces. Added #undefs for all file-private #defines
 * to safe-ify inclusion from/with other files.
 * 
*/

#include <time.h>
/* #include <sys/time.h> */
#include <string.h> /* strchr() and friends */
#include <stdio.h> /* sprintf() */
#include <ctype.h> /* toupper(), islower() */

#include "fossil-scm/fossil-config.h"
#include "fossil-scm/fossil-util.h"

#define HAVE_GET_TZ_NAME 0
#if HAVE_GET_TZ_NAME
extern char *get_tz_name();
#endif

#if !defined(BSD) && !defined(_MSC_VER)
extern void tzset (void);
#endif
static int weeknumber (const struct tm *timeptr, int firstweekday);

/* defaults: season to taste */
#define SYSV_EXT    1    /* stuff in System V ascftime routine */
#define SUNOS_EXT    1    /* stuff in SunOS strftime routine */
#define POSIX2_DATE    1    /* stuff in Posix 1003.2 date command */
#define VMS_EXT        1    /* include %v for VMS date format */

#if 0
#define POSIX_SEMANTICS    1    /* call tzset() if TZ changes */
#endif

#ifdef POSIX_SEMANTICS
#include <stdlib.h> /* malloc() and friends */
#endif

#if defined(POSIX2_DATE)
#if ! defined(SYSV_EXT)
#define SYSV_EXT    1
#endif
#if ! defined(SUNOS_EXT)
#define SUNOS_EXT    1
#endif
#endif

#if defined(POSIX2_DATE)
static int iso8601wknum(const struct tm *timeptr);
#endif

#undef strchr    /* avoid AIX weirdness */


#ifdef __GNUC__
#define inline    __inline__
#else
#define inline    /**/
#endif

#define range(low, item, hi)    maximum(low, minimum(item, hi))

/* minimum --- return minimum of two numbers */

static inline int
minimum(int a, int b)
{
    return (a < b ? a : b);
}

/* maximum --- return maximum of two numbers */

static inline int
maximum(int a, int b)
{
    return (a > b ? a : b);
}

/* strftime --- produce formatted time */

fsl_size_t
fsl_strftime(char *s, fsl_size_t maxsize, const char *format, const struct tm *timeptr)
{
    char *endp = s + maxsize;
    char *start = s;
    char tbuf[100];
    int i;
    static short first = 1;
#ifdef POSIX_SEMANTICS
    static char *savetz = NULL;
    static int savetzlen = 0;
    char *tz;
#endif /* POSIX_SEMANTICS */

    /* various tables, useful in North America */
    static char *days_a[] = {
        "Sun", "Mon", "Tue", "Wed",
        "Thu", "Fri", "Sat",
    };
    static char *days_l[] = {
        "Sunday", "Monday", "Tuesday", "Wednesday",
        "Thursday", "Friday", "Saturday",
    };
    static char *months_a[] = {
        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
    };
    static char *months_l[] = {
        "January", "February", "March", "April",
        "May", "June", "July", "August", "September",
        "October", "November", "December",
    };
    static char *ampm[] = { "AM", "PM", };

    if (s == NULL || format == NULL || timeptr == NULL || maxsize == 0)
        return 0;

    if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize)
        return 0;

#ifndef POSIX_SEMANTICS
    if (first) {
        tzset();
        first = 0;
    }
#else    /* POSIX_SEMANTICS */
    tz = getenv("TZ");
    if (first) {
        if (tz != NULL) {
            int tzlen = strlen(tz);

            savetz = (char *) malloc(tzlen + 1);
            if (savetz != NULL) {
                savetzlen = tzlen + 1;
                strcpy(savetz, tz);
            }
        }
        tzset();
        first = 0;
    }
    /* if we have a saved TZ, and it is different, recapture and reset */
    if (tz && savetz && (tz[0] != savetz[0] || strcmp(tz, savetz) != 0)) {
        i = strlen(tz) + 1;
        if (i > savetzlen) {
            savetz = (char *) realloc(savetz, i);
            if (savetz) {
                savetzlen = i;
                strcpy(savetz, tz);
            }
        } else
            strcpy(savetz, tz);
        tzset();
    }
#endif    /* POSIX_SEMANTICS */

    for (; *format && s < endp - 1; format++) {
        tbuf[0] = '\0';
        if (*format != '%') {
            *s++ = *format;
            continue;
        }
    again:
        switch (*++format) {
        case '\0':
            *s++ = '%';
            goto out;

        case '%':
            *s++ = '%';
            continue;

        case 'a':    /* abbreviated weekday name */
            if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6)
                strcpy(tbuf, "?");
            else
                strcpy(tbuf, days_a[timeptr->tm_wday]);
            break;

        case 'A':    /* full weekday name */
            if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6)
                strcpy(tbuf, "?");
            else
                strcpy(tbuf, days_l[timeptr->tm_wday]);
            break;

#ifdef SYSV_EXT
        case 'h':    /* abbreviated month name */
#endif
        case 'b':    /* abbreviated month name */
            if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11)
                strcpy(tbuf, "?");
            else
                strcpy(tbuf, months_a[timeptr->tm_mon]);
            break;

        case 'B':    /* full month name */
            if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11)
                strcpy(tbuf, "?");
            else
                strcpy(tbuf, months_l[timeptr->tm_mon]);
            break;

        case 'c':    /* appropriate date and time representation */
            sprintf(tbuf, "%s %s %2d %02d:%02d:%02d %d",
                days_a[range(0, timeptr->tm_wday, 6)],
                months_a[range(0, timeptr->tm_mon, 11)],
                range(1, timeptr->tm_mday, 31),
                range(0, timeptr->tm_hour, 23),
                range(0, timeptr->tm_min, 59),
                range(0, timeptr->tm_sec, 61),
                timeptr->tm_year + 1900);
            break;

        case 'd':    /* day of the month, 01 - 31 */
            i = range(1, timeptr->tm_mday, 31);
            sprintf(tbuf, "%02d", i);
            break;

        case 'H':    /* hour, 24-hour clock, 00 - 23 */
            i = range(0, timeptr->tm_hour, 23);
            sprintf(tbuf, "%02d", i);
            break;

        case 'I':    /* hour, 12-hour clock, 01 - 12 */
            i = range(0, timeptr->tm_hour, 23);
            if (i == 0)
                i = 12;
            else if (i > 12)
                i -= 12;
            sprintf(tbuf, "%02d", i);
            break;

        case 'j':    /* day of the year, 001 - 366 */
            sprintf(tbuf, "%03d", timeptr->tm_yday + 1);
            break;

        case 'm':    /* month, 01 - 12 */
            i = range(0, timeptr->tm_mon, 11);
            sprintf(tbuf, "%02d", i + 1);
            break;

        case 'M':    /* minute, 00 - 59 */
            i = range(0, timeptr->tm_min, 59);
            sprintf(tbuf, "%02d", i);
            break;

        case 'p':    /* am or pm based on 12-hour clock */
            i = range(0, timeptr->tm_hour, 23);
            if (i < 12)
                strcpy(tbuf, ampm[0]);
            else
                strcpy(tbuf, ampm[1]);
            break;

        case 'S':    /* second, 00 - 61 */
            i = range(0, timeptr->tm_sec, 61);
            sprintf(tbuf, "%02d", i);
            break;

        case 'U':    /* week of year, Sunday is first day of week */
            sprintf(tbuf, "%d", weeknumber(timeptr, 0));
            break;

        case 'w':    /* weekday, Sunday == 0, 0 - 6 */
            i = range(0, timeptr->tm_wday, 6);
            sprintf(tbuf, "%d", i);
            break;

        case 'W':    /* week of year, Monday is first day of week */
            sprintf(tbuf, "%d", weeknumber(timeptr, 1));
            break;

        case 'x':    /* appropriate date representation */
            sprintf(tbuf, "%s %s %2d %d",
                days_a[range(0, timeptr->tm_wday, 6)],
                months_a[range(0, timeptr->tm_mon, 11)],
                range(1, timeptr->tm_mday, 31),
                timeptr->tm_year + 1900);
            break;

        case 'X':    /* appropriate time representation */
            sprintf(tbuf, "%02d:%02d:%02d",
                range(0, timeptr->tm_hour, 23),
                range(0, timeptr->tm_min, 59),
                range(0, timeptr->tm_sec, 61));
            break;

        case 'y':    /* year without a century, 00 - 99 */
            i = timeptr->tm_year % 100;
            sprintf(tbuf, "%d", i);
            break;

        case 'Y':    /* year with century */
            sprintf(tbuf, "%d", 1900 + timeptr->tm_year);
            break;

#if HAVE_GET_TZ_NAME
          case 'Z':    /* time zone name or abbrevation */
            strcpy(tbuf, get_tz_name(timeptr));
            break;
#endif
            
#ifdef SYSV_EXT
        case 'n':    /* same as \n */
            tbuf[0] = '\n';
            tbuf[1] = '\0';
            break;

        case 't':    /* same as \t */
            tbuf[0] = '\t';
            tbuf[1] = '\0';
            break;

        case 'D':    /* date as %m/%d/%y */
            fsl_strftime(tbuf, sizeof tbuf, "%m/%d/%y", timeptr);
            break;

        case 'e':    /* day of month, blank padded */
            sprintf(tbuf, "%2d", range(1, timeptr->tm_mday, 31));
            break;

        case 'r':    /* time as %I:%M:%S %p */
            fsl_strftime(tbuf, sizeof tbuf, "%I:%M:%S %p", timeptr);
            break;

        case 'R':    /* time as %H:%M */
            fsl_strftime(tbuf, sizeof tbuf, "%H:%M", timeptr);
            break;

        case 'T':    /* time as %H:%M:%S */
            fsl_strftime(tbuf, sizeof tbuf, "%H:%M:%S", timeptr);
            break;
#endif

#ifdef SUNOS_EXT
        case 'k':    /* hour, 24-hour clock, blank pad */
            sprintf(tbuf, "%2d", range(0, timeptr->tm_hour, 23));
            break;

        case 'l':    /* hour, 12-hour clock, 1 - 12, blank pad */
            i = range(0, timeptr->tm_hour, 23);
            if (i == 0)
                i = 12;
            else if (i > 12)
                i -= 12;
            sprintf(tbuf, "%2d", i);
            break;
#endif


#ifdef VMS_EXT
        case 'v':    /* date as dd-bbb-YYYY */
            sprintf(tbuf, "%2d-%3.3s-%4d",
                range(1, timeptr->tm_mday, 31),
                months_a[range(0, timeptr->tm_mon, 11)],
                timeptr->tm_year + 1900);
            for (i = 3; i < 6; i++)
                if (islower((int)tbuf[i]))
                    tbuf[i] = toupper((int)tbuf[i]);
            break;
#endif


#ifdef POSIX2_DATE
        case 'C':
            sprintf(tbuf, "%02d", (timeptr->tm_year + 1900) / 100);
            break;


        case 'E':
        case 'O':
            /* POSIX locale extensions, ignored for now */
            goto again;

        case 'V':    /* week of year according ISO 8601 */
#if defined(GAWK) && defined(VMS_EXT)
        {
            extern int do_lint;
            extern void warning();
            static int warned = 0;

            if (! warned && do_lint) {
                warned = 1;
                warning(
    "conversion %%V added in P1003.2/11.3; for VMS style date, use %%v");
            }
        }
#endif
            sprintf(tbuf, "%d", iso8601wknum(timeptr));
            break;

        case 'u':
        /* ISO 8601: Weekday as a decimal number [1 (Monday) - 7] */
            sprintf(tbuf, "%d", timeptr->tm_wday == 0 ? 7 :
                    timeptr->tm_wday);
            break;
#endif    /* POSIX2_DATE */
        default:
            tbuf[0] = '%';
            tbuf[1] = *format;
            tbuf[2] = '\0';
            break;
        }
        i = strlen(tbuf);
        if (i){
            if (s + i < endp - 1) {
                strcpy(s, tbuf);
                s += i;
            } else return 0;
            /* reminder: above IF originally had ambiguous else
               placement (no braces).  This placement _appears_ to be
               correct.*/
        }
    }
out:
    if (s < endp && *format == '\0') {
        *s = '\0';
        return (s - start);
    } else
        return 0;
}

#ifdef POSIX2_DATE
/* iso8601wknum --- compute week number according to ISO 8601 */

static int
iso8601wknum(const struct tm *timeptr)
{
    /*
     * From 1003.2 D11.3:
     *    If the week (Monday to Sunday) containing January 1
     *    has four or more days in the new year, then it is week 1;
     *    otherwise it is week 53 of the previous year, and the
     *    next week is week 1.
     *
     * ADR: This means if Jan 1 was Monday through Thursday,
     *    it was week 1, otherwise week 53.
     */

    int simple_wknum, jan1day, diff, ret;

    /* get week number, Monday as first day of the week */
    simple_wknum = weeknumber(timeptr, 1) + 1;

    /*
     * With thanks and tip of the hatlo to tml@tik.vtt.fi
     *
     * What day of the week does January 1 fall on?
     * We know that
     *    (timeptr->tm_yday - jan1.tm_yday) MOD 7 ==
     *        (timeptr->tm_wday - jan1.tm_wday) MOD 7
     * and that
     *     jan1.tm_yday == 0
     * and that
     *     timeptr->tm_wday MOD 7 == timeptr->tm_wday
     * from which it follows that. . .
      */
    jan1day = timeptr->tm_wday - (timeptr->tm_yday % 7);
    if (jan1day < 0)
        jan1day += 7;

    /*
     * If Jan 1 was a Monday through Thursday, it was in
     * week 1.  Otherwise it was last year's week 53, which is
     * this year's week 0.
     */
    if (jan1day >= 1 && jan1day <= 4)
        diff = 0;
    else
        diff = 1;
    ret = simple_wknum - diff;
    if (ret == 0)    /* we're in the first week of the year */
        ret = 53;
    return ret;
}
#endif

/* weeknumber --- figure how many weeks into the year */

/* With thanks and tip of the hatlo to ado@elsie.nci.nih.gov */

static int
weeknumber(const struct tm *timeptr, int firstweekday)
{
    if (firstweekday == 0)
        return (timeptr->tm_yday + 7 - timeptr->tm_wday) / 7;
    else
        return (timeptr->tm_yday + 7 -
            (timeptr->tm_wday ? (timeptr->tm_wday - 1) : 6)) / 7;
}

#undef SYSV_EXT
#undef SUNOS_EXT
#undef POSIX2_DATE
#undef VMS_EXT
#undef POSIX_SEMANTICS
#undef adddecl
#undef inline
#undef range
#undef maximum
#undef minimum
#undef HAVE_GET_TZ_NAME
#undef GAWK


/**
   A convenience form of fsl_strftime() which takes its timestamp in
   the form of a Unix Epoc time.
*/
fsl_size_t fsl_strftime_unix(char * dest, fsl_size_t destLen, char const * format,
                             fsl_time_t epochTime, bool convertToLocal){
  time_t orig = (time_t)epochTime;
  struct tm * tim = convertToLocal ? localtime(&orig) : gmtime(&orig);
  return fsl_strftime( dest, destLen, format, tim );
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/tag.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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

  
  *****************************************************************************
  This file implements tag-related parts of the library.
*/
#include "fossil-scm/fossil-internal.h"
#include <assert.h>

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


void fsl_card_T_clean(fsl_card_T *t){
  if(t){
    fsl_free(t->uuid);
    t->uuid = NULL;
    fsl_free(t->name);
    t->name = NULL;
    fsl_free(t->value);
    t->value = NULL;
    *t = fsl_card_T_empty;
  }
}

void fsl_card_T_free(fsl_card_T *t){
  if(t){
    fsl_card_T_clean(t);
    fsl_free(t);
  }
}

fsl_card_T * fsl_card_T_malloc(fsl_tagtype_e tagType,
                               char const * uuid,
                               char const * name,
                               char const * value){
  fsl_card_T * t;
  int const uuidLen = uuid ? fsl_is_uuid(uuid) : 0;
  if(uuid && !uuidLen) return NULL;
  t = (fsl_card_T *)fsl_malloc(sizeof(fsl_card_T));
  if(t){
    int rc = 0;
    *t = fsl_card_T_empty;
    t->type = tagType;
    if(uuid && *uuid){
      t->uuid = fsl_strndup(uuid, uuidLen);
      if(!t->uuid) rc = FSL_RC_OOM;
    }
    if(!rc && name && *name){
      t->name = fsl_strdup(name);
      if(!t->name){
        rc = FSL_RC_OOM;
      }
    }
    if(!rc && value && *value){
      t->value = fsl_strdup(value);
      if(!t->value){
        rc = FSL_RC_OOM;
      }
    }
    if(rc){
      fsl_card_T_free(t);
      t = NULL;
    }
  }
  return t;
}

fsl_id_t fsl_tag_id( fsl_cx * f, char const * tag, bool create ){
  fsl_db * db = fsl_cx_db_repo(f);
  int64_t id = 0;
  int rc;
  if(!db || !tag) return FSL_RC_MISUSE;
  else if(!*tag) return FSL_RC_RANGE;
  rc = fsl_db_get_int64( db, &id,
                         "SELECT tagid FROM tag WHERE tagname=%Q",
                         tag);
  if(!rc && (0==id) && create){
    /* Not found - create one. */
    rc = fsl_db_exec(db, "INSERT INTO tag(tagname) VALUES(%Q)",
                     tag);
    if(!rc) id = fsl_db_last_insert_id(db);
  }

  if(rc){
    assert(0==id);
    fsl_cx_uplift_db_error( f, db );
    id = -1;
  }
  return id;
    
}

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){
  int rc;
  fsl_pq queue = fsl_pq_empty     /* Queue of artifacts to be tagged */;
  fsl_stmt s = fsl_stmt_empty     /* Query the children of :pid to which to propagate */;
  fsl_stmt ins = fsl_stmt_empty   /* INSERT INTO tagxref */;
  fsl_stmt eventupdate = fsl_stmt_empty  /* UPDATE event */;
  fsl_db * const db = fsl_needs_repo(f);

  assert(FSL_TAGTYPE_CANCEL==tagType || FSL_TAGTYPE_PROPAGATING==tagType);
  assert(f);
  assert(db);
  assert(pid>0);
  assert(tagid>0);

  if((pid<=0 || tagid<=0)
     ||(FSL_TAGTYPE_PROPAGATING!=tagType && FSL_TAGTYPE_CANCEL!=tagType)
     || (FSL_TAGTYPE_PROPAGATING==tagType && origId<=0)){
    return FSL_RC_RANGE;
  }
  else if(!db) return FSL_RC_NOT_A_REPO;

  rc = fsl_pq_insert(&queue, pid, 0.0, NULL);
  if(rc) return rc;

  rc = fsl_db_prepare(db, &s,
                      "SELECT cid, plink.mtime,"
                      "       coalesce(srcid=0 AND "
                      "       tagxref.mtime<:mtime, %d) AS doit"
                      "  FROM plink LEFT JOIN tagxref "
                      "       ON cid=rid AND tagid=%"FSL_ID_T_PFMT
                      " WHERE pid=:pid AND isprim",
                      tagType==FSL_TAGTYPE_PROPAGATING,
                      (fsl_id_t)tagid);
  if(rc) goto end;
  rc = fsl_stmt_bind_double_name(&s, ":mtime", mtime);

  if(FSL_TAGTYPE_PROPAGATING==tagType){
    /* Set the propagated tag marker on artifact :rid */
    assert(origId>0);
    rc = fsl_db_prepare(db, &ins,
                        "REPLACE INTO tagxref("
                        "  tagid, tagtype, srcid, "
                        "  origid, value, mtime, rid"
                        ") VALUES("
                        "%"FSL_ID_T_PFMT"," /* tagid */
                        "%d,"  /*tagtype*/
                        "0," /*srcid*/
                        "%"FSL_ID_T_PFMT"," /*origId */
                        "%Q," /* zValue */
                        ":mtime,"
                        ":rid"
                        ")",
                        (fsl_id_t)tagid,
                        (int)FSL_TAGTYPE_PROPAGATING,
                        (fsl_id_t)origId, zValue);
    if(!rc) rc = fsl_stmt_bind_double_name(&ins, ":mtime", mtime);
  }else{
    /* Remove all references to the tag from checkin :rid */
    zValue = NULL;
    rc = fsl_db_prepare(db, &ins,
                        "DELETE FROM tagxref WHERE "
                        "tagid=%"FSL_ID_T_PFMT
                        " AND rid=:rid", (fsl_id_t)tagid);
  }
  if(rc) goto end;
  if( tagid==FSL_TAGID_BGCOLOR ){
    rc = fsl_db_prepare(db, &eventupdate,
                        "UPDATE event SET bgcolor=%Q "
                        "WHERE objid=:rid", zValue);
    if(rc) goto end;
  }

  while( 0 != (pid = fsl_pq_extract(&queue,NULL))){
    fsl_stmt_bind_id_name(&s, ":pid", pid);
#if 0
    MARKER(("Walking over pid %"FSL_ID_T_PFMT
            ", queue.used=%"FSL_SIZE_T_PFMT"\n", pid, queue.used));
#endif
    while( !rc && (FSL_RC_STEP_ROW == fsl_stmt_step(&s)) ){
      int32_t const doit = fsl_stmt_g_int32(&s, 2);
      if(doit){
        fsl_id_t cid = fsl_stmt_g_id(&s, 0);
        double mtime = fsl_stmt_g_double(&s,1);
        assert(cid>0);
        assert(mtime>0.0);
        rc = fsl_pq_insert(&queue, cid, mtime, NULL);
        if(!rc) rc = fsl_stmt_bind_id_name(&ins, ":rid", cid);
        if(rc) goto end;
        else {
          rc = fsl_stmt_step(&ins);
          if(FSL_RC_STEP_DONE != rc) goto end;
          rc = 0;
        }
        fsl_stmt_reset(&ins);
        if( FSL_TAGID_BGCOLOR == tagid ){
          rc = fsl_stmt_bind_id_name(&eventupdate, ":rid", cid);
          if(!rc){
            rc = fsl_stmt_step(&eventupdate);
            if(FSL_RC_STEP_DONE != rc) goto end;
            rc = 0;
          }
          fsl_stmt_reset(&eventupdate);
        }else if( FSL_TAGID_BRANCH == tagid ){
          rc = fsl_repo_leaf_eventually_check(f, cid);
        }
      }
    }
    fsl_stmt_reset(&s);
  }
  end:
  fsl_stmt_finalize(&s);
  fsl_stmt_finalize(&ins);
  fsl_stmt_finalize(&eventupdate);
  fsl_pq_clear(&queue);
  return rc;
  
}

int fsl_tag_propagate_all(fsl_cx * f, fsl_id_t pid){
  fsl_stmt q = fsl_stmt_empty;
  int rc;
  fsl_db * const db = fsl_cx_db_repo(f);
  if(!f) return FSL_RC_MISUSE;
  else if(pid<=0) return FSL_RC_RANGE;
  assert(db);
  rc = fsl_db_prepare(db, &q,
                      "SELECT tagid, tagtype, mtime, "
                      "value, origid FROM tagxref"
                      " WHERE rid=%"FSL_ID_T_PFMT,
                      pid);
  while( !rc && (FSL_RC_STEP_ROW == fsl_stmt_step(&q)) ){
    fsl_id_t const tagid = fsl_stmt_g_id(&q, 0);
    int32_t tagtype = fsl_stmt_g_int32(&q, 1);
    double const mtime = fsl_stmt_g_double(&q, 2);
    const char *zValue = fsl_stmt_g_text(&q, 3, NULL);
    fsl_id_t const origid = fsl_stmt_g_id(&q, 4);
    if( FSL_TAGTYPE_ADD==tagtype ) tagtype = FSL_TAGTYPE_CANCEL
      /* For propagating purposes */;
    rc = fsl_tag_propagate(f, tagtype, pid, tagid,
                           origid, zValue, mtime);
  }
  fsl_stmt_finalize(&q);
  return rc;
}


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 ){
  fsl_db * db = f ? fsl_cx_db_repo(f) : NULL;
  fsl_stmt q = fsl_stmt_empty;
  fsl_id_t tagid;
  int rc = 0;
  char const * zCol;
  if(!f || !zTag) return FSL_RC_MISUSE;
  else if(!db) return FSL_RC_NOT_A_REPO;
  tagid = fsl_tag_id(f, zTag, 1);
  if(tagid<0){
    assert(f->error.code);
    return f->error.code;
  }
  if( mtime<=0.0 ){
    mtime = fsl_db_julian_now(db);
    if(mtime<0) return FSL_RC_DB;
  }

  rc = fsl_db_prepare(db, &q, /* TODO: cached query */
                      "SELECT 1 FROM tagxref"
                      " WHERE tagid=%"FSL_ID_T_PFMT
                      "   AND rid=%"FSL_ID_T_PFMT
                      "   AND mtime>=?",
                      (fsl_id_t)tagid, (fsl_id_t)rid);
  if(!rc) rc = fsl_stmt_bind_double(&q, 1, mtime);
  if(!rc) rc = fsl_stmt_step(&q);
  if( FSL_RC_STEP_ROW == rc ){
    /*
      Another entry that is more recent already exists. Do nothing.

      Reminder: the above policy is from the original implementation.
      We might(?) want to return FSL_RC_ACCESS or
      FSL_RC_ALREADY_EXISTS here. The current behaviour seems harmless
      enough, though.
    */
    if(outRid) *outRid = tagid;
    fsl_stmt_finalize(&q);
    return 0;
  }else if(FSL_RC_STEP_DONE != rc){
    goto end;
  }
  fsl_stmt_finalize(&q);
  rc = fsl_db_prepare(db, &q,
                      "REPLACE INTO tagxref"
                      "(tagid,tagtype,srcId,origid,value,mtime,rid) "
                      "VALUES("
                      "%"FSL_ID_T_PFMT"," /* tagid */
                      "%d," /* tagtype */
                      "%"FSL_ID_T_PFMT"," /* srcid */
                      "%"FSL_ID_T_PFMT"," /* rid */
                      "%Q," /* zValue */
                      "?," /* mtime */
                      "%"FSL_ID_T_PFMT")" /* rid again */,
                      (fsl_id_t)tagid, (int)tagtype,
                      (fsl_id_t)srcId,
                      (fsl_id_t)rid, zValue, (fsl_id_t)rid
                      );
  if(!rc) fsl_stmt_bind_double(&q, 1, mtime);
  if(!rc) rc = fsl_stmt_step(&q);
  if(FSL_RC_STEP_DONE != rc) goto end;
  rc = 0;
  fsl_stmt_finalize(&q);

  if(FSL_TAGID_BRANCH == tagid ){
    rc = fsl_repo_leaf_eventually_check(f, rid);
    if(rc) goto end;
  }
#if 0
  /* Historical: we have valid use cases for the
     value here.
  */
  else if(FSL_TAGTYPE_CANCEL==tagtype){
    zValue = NULL;
  }
#endif

  zCol = NULL;
  switch(tagid){
    case FSL_TAGID_BGCOLOR:
      zCol = "bgcolor";
      break;
    case FSL_TAGID_COMMENT:
      zCol = "ecomment";
      break;
    case FSL_TAGID_USER: {
      zCol = "euser";
      break;
    }
    case FSL_TAGID_PRIVATE:
      rc = fsl_db_exec(db,
                       "INSERT OR IGNORE INTO "
                       "private(rid) VALUES"
                       "(%"FSL_ID_T_PFMT");",
                       (fsl_id_t)rid );
      if(rc) goto end;
      else break;
  }
  if( zCol ){
    rc = fsl_db_exec(db, "UPDATE event SET %s=%Q "
                     "WHERE objid=%"FSL_ID_T_PFMT,
                     zCol, zValue, (fsl_id_t)rid);
    if(rc) goto end;
#if 0
    /*
      Legacy: i don't want this behaviour in the lib right now
      (possibly never). And don't want to port it yet, either :/.
     */
    if( tagid==FSL_TAGID_COMMENT ){
      char *zCopy = fsl_strdup(zValue);
      wiki_extract_links(zCopy, rid, 0, mtime, 1, WIKI_INLINE);
      free(zCopy);
    }
#endif
  }

  if( FSL_TAGID_DATE == tagid ){
    rc = fsl_db_exec(db, "UPDATE event "
                     "SET mtime=julianday(%Q),"
                     "    omtime=coalesce(omtime,mtime)"
                     "WHERE objid=%"FSL_ID_T_PFMT,
                     zValue, (fsl_id_t)rid);
    if(rc) goto end;
  }
  if( FSL_TAGTYPE_ADD == tagtype ) tagtype = FSL_TAGTYPE_CANCEL
    /* For propagation purposes */;
  rc = fsl_tag_propagate(f, tagtype, rid, tagid,
                         rid, zValue, mtime);
  end:
  if(rc){
    fsl_stmt_finalize(&q);
  }else{
    assert(!q.stmt);
    if(outRid) *outRid = tagid;
  }
  return rc;
}

int fsl_tag_sym( fsl_cx * f,
                 fsl_tagtype_e tagType,
                 char const * symToTag,
                 char const * tagName,
                 char const * tagValue,
                 char const * userName,
                 double mtime,
                 fsl_id_t * outId ){
  if(!f || !tagName || !symToTag || !userName) return FSL_RC_MISUSE;
  else if(!*tagName || !*userName || !*symToTag) return FSL_RC_RANGE;
  else{
    fsl_id_t resolvedRid = 0;
    int rc;
    rc = fsl_sym_to_rid( f, symToTag, FSL_SATYPE_ANY, &resolvedRid );
    if(!rc){
      assert(resolvedRid>0);
      rc = fsl_tag_an_rid(f, tagType, resolvedRid,
                          tagName, tagValue, userName,
                          mtime, outId);
    }
    return rc;
  }
}

int fsl_tag_an_rid( fsl_cx * f,
                    fsl_tagtype_e tagType,
                    fsl_id_t idToTag,
                    char const * tagName,
                    char const * tagValue,
                    char const * userName,
                    double mtime,
                    fsl_id_t * outId ){
  fsl_db * dbR = f ? fsl_cx_db_repo(f) : NULL;
  fsl_deck c = fsl_deck_empty;
  char * resolvedUuid = NULL;
  fsl_buffer mfout = fsl_buffer_empty;

  int rc;
  if(!f || !tagName || !userName) return FSL_RC_MISUSE;
  else if(!*tagName || !*userName || (idToTag<=0)) return FSL_RC_RANGE;
  else if(!dbR) return FSL_RC_NOT_A_REPO;

  if(mtime<=0) mtime = fsl_db_julian_now(dbR);

  resolvedUuid = fsl_rid_to_uuid(f, idToTag);
  if(!resolvedUuid){
    return fsl_cx_err_set(f, FSL_RC_RANGE,
                          "Could not resolve UUID for "
                          "rid %"FSL_ID_T_PFMT".",
                          (fsl_id_t)idToTag);
  }
  assert(fsl_is_uuid(resolvedUuid));

#if 0
  tagRid = fsl_tag_id(f, tagName, 1);
  if(tagRid<=0){
    rc = f->error.rc
      ? f->error.rc
      : fsl_cx_err_set(f, FSL_RC_ERROR,
                       "Unknown error while fetching "
                       "ID for tag [%s].",
                       tagName);
    goto end;
  }
#endif

  fsl_deck_init(f, &c, FSL_SATYPE_CONTROL);
  rc = fsl_deck_T_add( &c, tagType, resolvedUuid,
                       tagName, tagValue );
  if(rc) goto end;

  rc = fsl_deck_D_set( &c, mtime );
  if(rc) goto end;

  rc = fsl_deck_U_set( &c, userName );
  if(rc) goto end;

  rc = fsl_deck_save( &c, fsl_content_is_private(f, idToTag) );
  end:
  fsl_free(resolvedUuid);
  fsl_buffer_clear(&mfout);
  if(!rc && outId){
    assert(c.rid>0);
    *outId = c.rid;
  }
  fsl_deck_clean(&c);
  return rc;
}

int fsl_branch_create(fsl_cx * f, fsl_branch_opt const * opt, fsl_id_t * newRid ){
  int rc;
  fsl_deck parent = fsl_deck_empty;
  fsl_deck deck = fsl_deck_empty;
  fsl_db * db = f ? fsl_needs_repo(f) : NULL;
  char const * user;
  char isPrivate;
  if(!f || !opt) return FSL_RC_MISUSE;
  else if(!db) return FSL_RC_NOT_A_REPO;
  else if(opt->basisRid<=0 || !opt->name || !*opt->name){
    return FSL_RC_MISUSE;
  }
  else if(! (user = opt->user ? opt->user : f->repo.user)){
    rc = fsl_cx_err_set(f, FSL_RC_MISUSE,
                        "Could not determine fossil user name "
                        "for new branch [%s].", opt->name);
  }


  rc = fsl_deck_load_rid(f, &parent, opt->basisRid, FSL_SATYPE_CHECKIN);
  if(rc) goto end;

  assert(parent.rid==opt->basisRid);

  fsl_deck_init(f, &deck, FSL_SATYPE_CHECKIN);

  if(parent.B.uuid){
    rc = fsl_deck_B_set(&deck, parent.B.uuid);
  }

  rc = fsl_deck_D_set(&deck, opt->mtime>0 ? opt->mtime : fsl_db_julian_now(db));
  if(rc) goto end;

  /*
    We cannot simply transfer the list of F-cards from parent to deck
    because their content pointers (when the deck is parsed using fsl_deck_parse2())
    points to memory in parent.content;
  */
  for( fsl_size_t i = 0; i < parent.F.used; ++i){
    fsl_card_F const * fc = &parent.F.list[i];
    rc = fsl_deck_F_add(&deck, fc->name, fc->uuid, fc->perm, fc->priorName);
      if(rc) goto end;
  }

  rc = fsl_deck_U_set(&deck, user);
  if(rc) goto end;

  rc = fsl_deck_P_add(&deck, parent.uuid);
  if(rc) goto end;

  if(opt->comment && *opt->comment){
    rc = fsl_deck_C_set(&deck, opt->comment, -1);
  }else{
    fsl_buffer c = fsl_buffer_empty;
    rc = fsl_buffer_appendf(&c, "Created branch [%s].", opt->name);
    if(!rc){
      rc = fsl_deck_C_set(&deck, (char const *)c.mem, (fsl_int_t)c.used);
    }
    fsl_buffer_clear(&c);
  }
  if(rc) goto end;


#if 0
  /* This adds almost 18MB of allocations to my small test code! */
  if(deck.F.list.used){
    rc = fsl_deck_R_calc(&deck);
    if(rc) goto end;
  }
#else
  rc = fsl_deck_R_set(&deck, parent.R);
  if(rc) goto end;
#endif

  isPrivate = fsl_content_is_private(f, parent.rid) ? 1 : opt->isPrivate;
  if(isPrivate){
    rc = fsl_deck_T_add(&deck, FSL_TAGTYPE_ADD,
                        NULL, "private", NULL);
    if(rc) goto end;
  }

  if(opt->bgColor && ('#'==*opt->bgColor)){
    rc = fsl_deck_T_add(&deck, FSL_TAGTYPE_ADD, NULL, "bgcolor",
                        opt->bgColor);
    if(rc) goto end;
  }

  rc = fsl_deck_T_add(&deck, FSL_TAGTYPE_PROPAGATING,
                      NULL, "branch", opt->name);
  if(!rc){
    /* Add tag named sym-BRANCHNAME... */
    fsl_buffer * buf = fsl_cx_scratchpad(f);
    rc = fsl_buffer_appendf(buf, "sym-%s", opt->name);
    if(!rc){
      rc = fsl_deck_T_add(&deck, FSL_TAGTYPE_PROPAGATING,
                          NULL, fsl_buffer_cstr(buf), NULL);
    }
    fsl_cx_scratchpad_yield(f, buf);
  }
  if(rc) goto end;

#if 1
  rc = fsl_db_transaction_begin(db);
  if(rc) goto end;
  else{
    /* cancel all other symbolic tags (branch tags) */
    fsl_stmt q = fsl_stmt_empty;
    rc = fsl_db_prepare(db, &q,
                        "SELECT tagname FROM tagxref, tag"
                        " WHERE tagxref.rid=%"FSL_ID_T_PFMT
                        " AND tagxref.tagid=tag.tagid"
                        "   AND tagtype>0 AND tagname GLOB 'sym-*'"
                        " ORDER BY tagname",
                        (fsl_id_t)parent.rid);
    if(rc) goto end;
    while( FSL_RC_STEP_ROW==fsl_stmt_step(&q) ){
      const char *zTag = fsl_stmt_g_text(&q, 0, NULL);
      rc = fsl_deck_T_add(&deck, FSL_TAGTYPE_CANCEL,
                          NULL, zTag, "cancelled by branch.");
      if(rc) break;
    }
    fsl_stmt_finalize(&q);
    if(!rc){
      rc = fsl_deck_save(&deck, isPrivate);
      if(!rc){
        assert(deck.rid>0);
        rc = fsl_db_exec(db, "INSERT OR IGNORE INTO "
                         "unsent VALUES(%"FSL_ID_T_PFMT")",
                         (fsl_id_t)deck.rid);
        if(!rc){
          /* Make the parent a delta of this one. */
          rc = fsl_content_deltify(f, parent.rid, deck.rid, 0);
        }
      }
    }
    if(!rc) rc = fsl_db_transaction_commit(db);
    else fsl_db_transaction_rollback(db);
    if(rc) goto end;
  }
#else
  MARKER(("Generating (not saving) branch artifact:\n"));
  rc = fsl_deck_unshuffle(&deck, 0);
  if(rc) goto end;
  rc = fsl_deck_output(&deck, fsl_output_f_FILE, stdout, &f->error);
  if(rc) goto end;
#endif

  end:
  if(!rc && newRid) *newRid = deck.rid;
  else if(rc && !f->error.code){
    if(db->error.code) fsl_cx_uplift_db_error(f,db);
  }
  fsl_deck_finalize(&parent);
  fsl_deck_finalize(&deck);
  return rc;
}
                       

#undef MARKER
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/ticket.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 implements ticket-related parts of the library.
*/
#include "fossil-scm/fossil-internal.h"
#include <assert.h>
#include <string.h> /* memcmp() */

int fsl_cx_ticket_create_table(fsl_cx * f){
  fsl_db * db = f ? fsl_needs_repo(f) : NULL;
  int rc;
  if(!f) return FSL_RC_MISUSE;
  else if(!db) return FSL_RC_NOT_A_REPO;
  rc = fsl_db_exec_multi(db,
                         "DROP TABLE IF EXISTS ticket;"
                         "DROP TABLE IF EXISTS ticketchng;"
                         );
  if(!rc){
    fsl_buffer * buf = fsl_cx_scratchpad(f);
    rc = fsl_cx_schema_ticket(f, buf);
    if(!rc){
      rc = fsl_db_exec_multi(db, "%b", buf);
    }
    fsl_cx_scratchpad_yield(f, buf);
  }
  if(rc && db->error.code && !f->error.code){
    fsl_cx_uplift_db_error(f, db);
  }
  return rc;
}

static int fsl_tkt_field_id(fsl_list const * jli, const char *zFieldName){
  int i;
  fsl_card_J const * jc;
  for(i=0; i<(int)jli->used; ++i){
    jc = (fsl_card_J const *)jli->list[i];
    if( !fsl_strcmp(zFieldName, jc->field) ) return i;
  }
  return -1;
}

int fsl_cx_ticket_load_fields(fsl_cx * f, bool forceReload){
  fsl_stmt q = fsl_stmt_empty;
  int i, rc = 0;
  fsl_list * li = &f->ticket.customFields;
  fsl_card_J * jc;
  fsl_db * db;
  if(li->used){
    if(!forceReload) return 0;
    fsl_card_J_list_free(li, 0);
    /* Fall through and reload ... */
  }
  if( !(db = fsl_needs_repo(f)) ){
    return FSL_RC_NOT_A_REPO;
  }
  rc = fsl_db_prepare(db, &q, "PRAGMA table_info(ticket)");
  if(!rc) while( FSL_RC_STEP_ROW==fsl_stmt_step(&q) ){
    char const * zFieldName = fsl_stmt_g_text(&q, 1, NULL);
    f->ticket.hasTicket = 1;
    if( 0==memcmp(zFieldName,"tkt_", 4)){
      if( 0==fsl_strcmp(zFieldName,"tkt_ctime")) f->ticket.hasCTime = 1;
      continue;
    }
    jc = fsl_card_J_malloc(0, zFieldName, NULL);
    if(!jc){
      rc = FSL_RC_OOM;
      break;
    }
    jc->flags = FSL_CARD_J_TICKET;
    rc = fsl_list_append(li, jc);
    if(rc){
      fsl_card_J_free(jc);
      break;
    }
  }
  fsl_stmt_finalize(&q);
  if(rc) goto end;

  rc = fsl_db_prepare(db, &q, "PRAGMA table_info(ticketchng)");
  if(!rc) while( FSL_RC_STEP_ROW==fsl_stmt_step(&q) ){
    char const * zFieldName = fsl_stmt_g_text(&q, 1, NULL);
    f->ticket.hasChng = 1;
    if( 0==memcmp(zFieldName,"tkt_", 4)){
      if( 0==fsl_strcmp(zFieldName,"tkt_rid")) f->ticket.hasChngRid = 1;
      continue;
    }
    if( (i=fsl_tkt_field_id(li, zFieldName)) >= 0){
      jc = (fsl_card_J*)li->list[i];
      jc->flags |= FSL_CARD_J_CHNG;
      continue;
    }
    jc = fsl_card_J_malloc(0, zFieldName, NULL);
    if(!jc){
      rc = FSL_RC_OOM;
      break;
    }
    jc->flags = FSL_CARD_J_CHNG;
    rc = fsl_list_append(li, jc);
    if(rc){
      fsl_card_J_free(jc);
      break;
    }
  }
  fsl_stmt_finalize(&q);
  end:
  if(!rc){
    fsl_list_sort(li, fsl_qsort_cmp_J_cards);
  }
  return rc;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


















































































































































































































































Deleted src/utf8.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
** Copyright (c) 2017 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains an implementation of SHA3 (Keccak) hashing.
*/
/**
   This copy has been modified slightly for use with the libfossil
   API.
*/
#include "fossil-scm/fossil.h"
#include "fossil-scm/fossil-internal.h"

#include <assert.h>
#include <stddef.h> /* NULL on linux */
#include <ctype.h>

#ifdef _WIN32
# include <windows.h>
#else
#ifdef __CYGWIN__
# include <sys/cygwin.h>
# define CP_UTF8 65001
  __declspec(dllimport) extern __stdcall int WideCharToMultiByte(int, int,
      const char *, int, const char *, int, const char *, const char *);
  __declspec(dllimport) extern __stdcall int MultiByteToWideChar(int, int,
      const char *, int, wchar_t*, int);
#endif
/* /Cygwin */
/* Assume Unix */
#include <stdlib.h> /* getenv() */
#endif


#if defined(__APPLE__) && !defined(WITHOUT_ICONV)
# include <iconv.h>
#endif

#ifdef _WIN32
char *fsl_mbcs_to_utf8(const char *zMbcs){
  extern char *sqlite3_win32_mbcs_to_utf8(const char*);
  return sqlite3_win32_mbcs_to_utf8(zMbcs);
}

void fossil_mbcs_free(char *zOld){
  sqlite3_free(zOld);
}
#endif /* _WIN32 */

void fsl_unicode_free(void *p){
  if(p) fsl_free(p);
}

char *fsl_unicode_to_utf8(const void *zUnicode){
#if defined(_WIN32) || defined(__CYGWIN__)
  int nByte = WideCharToMultiByte(CP_UTF8, 0, zUnicode, -1, 0, 0, 0, 0);
  char *zUtf = (char *)fsl_malloc( nByte );
  if( zUtf ){
    WideCharToMultiByte(CP_UTF8, 0, zUnicode, -1, zUtf, nByte, 0, 0);
  }
  return zUtf;
#else
  return fsl_strdup((char const *)zUnicode);  /* TODO: implement for unix */
#endif
}

void *fsl_utf8_to_unicode(const char *zUtf8){
#if defined(_WIN32) || defined(__CYGWIN__)
  int nByte = MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, 0, 0);
  wchar_t *zUnicode = (wchar_t *)fsl_malloc( nByte * 2 );
  if( zUnicode ){
    MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zUnicode, nByte);
  }
  return zUnicode;
#else
  return fsl_strdup(zUtf8);  /* TODO: implement for unix */
#endif
}

/*
   We find that the built-in isspace() function does not work for
   some international character sets.  So here is a substitute.
*/
char fsl_isspace(int c){
  return c==' ' || (c<='\r' && c>='\t');
}

/*
   Other replacements for ctype.h functions.
*/
char fsl_islower(int c){ return c>='a' && c<='z'; }
char fsl_isupper(int c){ return c>='A' && c<='Z'; }
char fsl_isdigit(int c){ return c>='0' && c<='9'; }
int fsl_tolower(int c){
  return fsl_isupper(c) ? c - 'A' + 'a' : c;
}
int fsl_toupper(int c){
  return fsl_islower(c) ? c - 'a' + 'A' : c;
}
char fsl_isalpha(int c){
  return (c>='a' && c<='z') || (c>='A' && c<='Z');
}
char fsl_isalnum(int c){
  return (c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9');
}


void fsl_filename_free(void *pOld){
#if defined(_WIN32)
  fsl_free(pOld);
#elif (defined(__APPLE__) && !defined(WITHOUT_ICONV)) || defined(__CYGWIN__)
  fsl_free(pOld);
#else
  /* No-op on all other unix */
#endif
}

char *fsl_filename_to_utf8(const void *zFilename){
#if defined(_WIN32)
  int nByte = WideCharToMultiByte(CP_UTF8, 0, zFilename, -1, 0, 0, 0, 0);
  char *zUtf = fsl_malloc( nByte );
  char *pUtf, *qUtf;
  if( zUtf==0 ){
    return 0;
  }
  WideCharToMultiByte(CP_UTF8, 0, zFilename, -1, zUtf, nByte, 0, 0);
  pUtf = qUtf = zUtf;
  while( *pUtf ) {
    if( *pUtf == (char)0xef ){
      wchar_t c = ((pUtf[1]&0x3f)<<6)|(pUtf[2]&0x3f);
      /* Only really convert it when the resulting char is in range. */
      if ( c && ((c < ' ') || wcschr(L"\"*:<>?|", c)) ){
        *qUtf++ = c; pUtf+=3; continue;
      }
    }
    *qUtf++ = *pUtf++;
  }
  *qUtf = 0;
  return zUtf;
#elif defined(__CYGWIN__)
  char *zOut;
  zOut = fsl_strdup(zFilename)
    /*
      Required for consistency with fsl_utf8_to_filename(),
      so that fsl_filename_free() can DTRT.
    */;
  return zOut;
#elif defined(__APPLE__) && !defined(WITHOUT_ICONV)
  char *zIn = (char*)zFilename;
  char *zOut;
  iconv_t cd;
  size_t n, x;
  for(n=0; zIn[n]>0 && zIn[n]<=0x7f; n++){}
  if( zIn[n]!=0 && (cd = iconv_open("UTF-8", "UTF-8-MAC"))!=(iconv_t)-1 ){
    char *zOutx;
    char *zOrig = zIn;
    size_t nIn, nOutx;
    nIn = n = fsl_strlen(zIn);
    nOutx = nIn+100;
    zOutx = zOut = (char *)fsl_malloc( nOutx+1 );
    if(!zOutx) return NULL;
    x = iconv(cd, &zIn, &nIn, &zOutx, &nOutx);
    if( x==(size_t)-1 ){
      fsl_free(zOut);
      zOut = fsl_strdup(zOrig);
    }else{
      zOut[n+100-nOutx] = 0;
    }
    iconv_close(cd);
  }else{
    zOut = fsl_strdup(zFilename);
  }
  return zOut;
#else
  return (char *)zFilename;  /* No-op on non-mac unix */
#endif
}

void *fsl_utf8_to_filename(const char *zUtf8){
#ifdef _WIN32
  /**
     Maintenance note 2021-03-24: fossil's counterpart of this has
     been extended since this code was ported:

     void *fossil_utf8_to_path(const char *zUtf8, int isDir)

     That isDir param is only for Windows and its only purpose is to
     ensure that the translated path is not within 12 bytes of
     MAX_PATH. That same effect can be had by simply always assuming
     that bool is true and sacrificing those 12 bytes and that far-edge
     case.

     Also, the newer code jumps through many hoops which seem
     unimportant for fossil, e.g. handling UNC-style paths.

     Porting that latter bit over requires someone who can at least
     test whether it compiles.
  */
  int nChar = MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, 0, 0);
  wchar_t *zUnicode = fsl_malloc( nChar * 2 );
  wchar_t *wUnicode = zUnicode;
  if( zUnicode==0 ){
    return 0;
  }
  MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zUnicode, nChar);
  /* If path starts with "<drive>:/" or "<drive>:\", don't translate the ':' */
  if( fsl_isalpha(zUtf8[0]) && zUtf8[1]==':'
           && (zUtf8[2]=='\\' || zUtf8[2]=='/')) {
    zUnicode[2] = '\\';
    wUnicode += 3;
  }
  while( *wUnicode != '\0' ){
    if ( (*wUnicode < ' ') || wcschr(L"\"*:<>?|", *wUnicode) ){
      *wUnicode |= 0xF000;
    }else if( *wUnicode == '/' ){
      *wUnicode = '\\';
    }
    ++wUnicode;
  }
  return zUnicode;
#elif defined(__CYGWIN__)
  char *zPath, *p;
  if( fsl_isalpha(zUtf8[0]) && (zUtf8[1]==':')
      && (zUtf8[2]=='\\' || zUtf8[2]=='/')) {
    /* win32 absolute path starting with drive specifier. */
    int nByte;
    wchar_t zUnicode[2000];
    wchar_t *wUnicode = zUnicode;
    MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zUnicode,
                        sizeof(zUnicode)/sizeof(zUnicode[0]));
    while( *wUnicode != '\0' ){
      if( *wUnicode == '/' ){
        *wUnicode = '\\';
      }
      ++wUnicode;
    }
    nByte = cygwin_conv_path(CCP_WIN_W_TO_POSIX, zUnicode, NULL, 0);
    zPath = (char *)fsl_malloc(nByte);
    if(!zPath) return NULL;
    cygwin_conv_path(CCP_WIN_W_TO_POSIX, zUnicode, zPath, nByte);
  }else{
    zPath = fsl_strdup(zUtf8);
    if(!zPath) return NULL;
    zUtf8 = p = zPath;
    while( (*p = *zUtf8++) != 0){
      if( *p++ == '\\' ) {
        p[-1] = '/';
      }
    }
  }
  return zPath;
#elif defined(__APPLE__) && !defined(WITHOUT_ICONV)
  return fsl_strdup(zUtf8)
    /* Why? Why not just act like Unix? */
    ;
#else
  return (void *)zUtf8;  /* No-op on unix */
#endif
}


char *fsl_getenv(const char *zName){
#ifdef _WIN32
  wchar_t *uName = (wchar_t *)fsl_utf8_to_unicode(zName);
  void *zValue = uName ? (void*)_wgetenv(uName) : NULL;
  fsl_free(uName);
#else
  char *zValue = (char *)getenv(zName);
#endif
  if( zValue ) zValue = fsl_filename_to_utf8(zValue);
  return zValue;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






























































































































































































































































































































































































































































































































































































Deleted src/vfile.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 contains some of the APIs dealing with the checkout state.
*/
#include "fossil-scm/fossil-internal.h"
#include "fossil-scm/fossil-hash.h"
#include "fossil-scm/fossil-checkout.h"
#include "fossil-scm/fossil-confdb.h"
#include <assert.h>

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

int fsl_vfile_load(fsl_cx * f, fsl_id_t vid,
                   bool clearOtherVersions,
                   uint32_t * missingCount){
  fsl_db * dbC = f ? fsl_needs_ckout(f) : NULL;
  fsl_db * dbR = dbC ? fsl_needs_repo(f) : NULL;
  fsl_deck d = fsl_deck_empty;
  fsl_stmt qIns = fsl_stmt_empty;
  fsl_stmt qRid = fsl_stmt_empty;
  int rc;
  bool alreadyHad;
  fsl_card_F const * fc;
  assert(dbC && "Must only be called when a checkout is opened.");
  assert(dbR && "Must only be called when a repo is opened.");
  if(!dbC) return FSL_RC_NOT_A_CKOUT;
  else if(!dbR) return FSL_RC_NOT_A_REPO;
  if(vid<=0) vid = f->ckout.rid;
  assert(vid>=0);

  rc = fsl_db_transaction_begin(dbC);
  if(rc) return rc;
  alreadyHad = fsl_db_exists(dbC,
                             "SELECT 1 FROM vfile WHERE vid=%"FSL_ID_T_PFMT,
                             vid);
  if(clearOtherVersions){
    /* Reminder to self: DO NOT clear vmerge here. Doing so will break
       merge tracking in the checkin process. */
    rc = fsl_vfile_unload_except(f, vid);
    if(rc) goto end;
  }
  if(alreadyHad){
    /* Already done. */
    rc = 0;
    goto end;
  }
  if(rc) goto end;

  if(0==vid){
    /* This is either misuse or an empty/initial repo with no
       checkins.  Let's assume the latter, since that's what triggered
       the addition of this check. */
    goto end;
  }

  rc = fsl_deck_load_rid(f, &d, vid, FSL_SATYPE_CHECKIN);
  if(rc) goto end;
  assert(d.rid==vid);
  rc = fsl_deck_F_rewind(&d);
  if(rc) goto end;
  rc = fsl_db_prepare(dbC, &qIns,
                      "INSERT INTO vfile"
                      "(vid,isexe,islink,rid,mrid,pathname,mhash) "
                      "VALUES(:vid,:isexe,:islink,:id,:id,:name,null)");
  if(rc) goto end;
  rc = fsl_db_prepare(dbR, &qRid,
                      "SELECT rid,size FROM blob WHERE uuid=?");
  if(rc) goto end;
  rc = fsl_stmt_bind_id_name(&qIns, ":vid", vid);
  while( !rc && !(rc=fsl_deck_F_next(&d, &fc)) && fc){
    fsl_id_t rid;
    int64_t size;
    assert(fc->uuid && "We couldn't get F-card deletions via fsl_deck_F_next()");
    if(fsl_uuid_is_shunned(f,fc->uuid)) continue;
    rc = fsl_stmt_bind_text(&qRid, 1, fc->uuid, -1, 0);
    if(rc) break;
    rc = fsl_stmt_step(&qRid);
    if(FSL_RC_STEP_ROW==rc){
      rid = fsl_stmt_g_id(&qRid,0);
      size = fsl_stmt_g_int64(&qRid,1);
    }else if(FSL_RC_STEP_DONE==rc){
      rid = 0;
      size = 0;
    }else{
      assert(qRid.db->error.code);
      rc = fsl_cx_uplift_db_error(f, qRid.db);
      break;
    }
    fsl_stmt_reset(&qRid);
    if( !rid || size<0 ){
      if(missingCount) ++*missingCount;
      continue;
    }
    fsl_stmt_bind_int32_name(&qIns, ":isexe",
                             (FSL_FILE_PERM_EXE & fc->perm) ? 1 : 0);
    fsl_stmt_bind_int32_name(&qIns, ":islink",
                             (FSL_FILE_PERM_LINK & fc->perm) ? 1 : 0);
    fsl_stmt_bind_id_name(&qIns, ":id", rid);
    rc = fsl_stmt_bind_text_name(&qIns, ":name", fc->name, -1, 0);
    if(rc) break;
    rc = fsl_stmt_step(&qIns);
    if(FSL_RC_STEP_DONE!=rc) break;
    else rc = 0;
    fsl_stmt_reset(&qIns);
  }
  
  end:
  fsl_stmt_finalize(&qIns);
  fsl_stmt_finalize(&qRid);
  /* Update f->ckout state and some db bits we need
     when changing the checkout. */
  if(!rc && vid>0){
    if(!alreadyHad){
      assert(d.rid>0);
      assert(d.uuid);
    }
  }
  fsl_deck_finalize(&d);
  if(rc) fsl_db_transaction_rollback(dbC);
  else rc = fsl_db_transaction_commit(dbC);
  if(rc && !f->error.code){
    if(dbC->error.code) fsl_cx_uplift_db_error(f, dbC);
    else if(dbR->error.code) fsl_cx_uplift_db_error(f, dbR);
  }
  return rc;
}

static int fsl_vfile_unload_impl(fsl_cx * f, fsl_id_t vid,
                                 bool oneVersion){
  fsl_db * const db = fsl_needs_ckout(f);
  if(!db) return FSL_RC_NOT_A_CKOUT;
  if(vid<=0) vid = f->ckout.rid;
  int const rc = fsl_db_exec(db, "DELETE FROM vfile "
                             "WHERE vid%s%" FSL_ID_T_PFMT
                             " /* %s() */",
                             oneVersion ? "=" : "<>",
                             vid, __func__);
  return rc ? fsl_cx_uplift_db_error2(f, db, rc) : 0;
}
int fsl_vfile_unload(fsl_cx * f, fsl_id_t vid){
  return fsl_vfile_unload_impl(f, vid, true);
}
int fsl_vfile_unload_except(fsl_cx * f, fsl_id_t vid){
  return fsl_vfile_unload_impl(f, vid, false);
}

/**
   Internal code de-duplifier for places which need to re-check a
   file's hash in order to be sure whether it was really
   modified. hashLen must be the length of the previous (db-side) hash
   of the file. This routine will hash that file using the same hash
   type. The new hash is appended to pTgt.

   Returns 0 on success.
*/
static int fsl_vfile_recheck_file_hash( fsl_cx * f, const char * zName,
                                        fsl_size_t hashLen, fsl_buffer * pTgt ){
  bool errReported = false;
  int rc = 0;
  if((fsl_size_t)FSL_STRLEN_SHA1==hashLen){
    rc = fsl_sha1sum_filename(zName, pTgt);
  }else if((fsl_size_t)FSL_STRLEN_K256==hashLen){
    rc = fsl_sha3sum_filename(zName, pTgt);
  }else{
    assert(!"This \"cannot happen\".");
    rc = fsl_cx_err_set(f, FSL_RC_CHECKSUM_MISMATCH,
                        "Cannot determine which hash to use for file: %s",
                        zName);
    errReported = true;
  }
  if(rc && !errReported && FSL_RC_OOM != rc){
    rc = fsl_cx_err_set(f, rc, "Error %s while hashing file: %s",
                        fsl_rc_cstr(rc), zName);
  }
  //if(!rc) assert(fsl_is_uuid_len(pTgt->used));
  return rc;
}


int fsl_vfile_changes_scan(fsl_cx * f, fsl_id_t vid, unsigned cksigFlags){
  fsl_stmt * stUpdate = NULL;
  fsl_stmt q = fsl_stmt_empty;
  int rc = 0;
  fsl_db * const db = fsl_needs_ckout(f);
  fsl_fstat fst = fsl_fstat_empty;
  fsl_size_t rootLen;
  fsl_buffer * fileCksum = fsl_cx_scratchpad(f);
  bool const useMtime = (cksigFlags & FSL_VFILE_CKSIG_HASH)==0
    && fsl_config_get_bool(f, FSL_CONFDB_REPO, true, "mtime-changes");
  if(!db) return FSL_RC_NOT_A_CKOUT;
  assert(f->ckout.dir);
  if(vid<=0) vid = f->ckout.rid;
  assert(vid>=0);
  rootLen = fsl_strlen(f->ckout.dir);
  assert(rootLen);

  rc = fsl_db_transaction_begin(db);
  if(rc) return rc;
  if(f->ckout.rid != vid){
    rc = fsl_vfile_load(f, vid,
                                 (FSL_VFILE_CKSIG_KEEP_OTHERS & cksigFlags)
                                 ? false : true,
                                 NULL);
  }
  if(rc) goto end;

#if 0
  MARKER(("changed/deleted vfile contents post load-from-rid:\n"));
  fsl_db_each( fsl_cx_db_ckout(f), fsl_stmt_each_f_dump, NULL,
               "SELECT vf.id, substr(b.uuid,0,8) hash, chnged, "
               "deleted, vf.pathname "
               "FROM vfile vf LEFT JOIN blob b "
               "ON b.rid=vf.rid "
               "WHERE vf.vid=%"FSL_ID_T_PFMT" "
               "AND (chnged<>0 OR pathname<>origname OR deleted<>0)"
               "ORDER BY vf.id", vid);
#endif

  rc = fsl_db_prepare(db, &q, "SELECT "
                      /*0*/"id,"
                      /*1*/"%Q || pathname,"
                      /*2*/"vfile.mrid,"
                      /*3*/"deleted,"
                      /*4*/"chnged,"
                      /*5*/"uuid,"
                      /*6*/"size,"
                      /*7*/"mtime,"
                      /*8*/"isexe,"
                      /*9*/"islink, "
                      /*10*/"CASE WHEN isexe THEN %d "
                            "WHEN islink THEN %d ELSE %d END "
                      "FROM vfile LEFT JOIN blob ON vfile.mrid=blob.rid "
                      "WHERE vid=%"FSL_ID_T_PFMT,
                      f->ckout.dir,
                      FSL_FILE_PERM_EXE, FSL_FILE_PERM_LINK,
                      FSL_FILE_PERM_REGULAR,
                      (fsl_id_t)vid);
  if(rc) goto end;
  while( fsl_stmt_step(&q) == FSL_RC_STEP_ROW ){
    fsl_id_t id, rid;
    char const * zName;
#ifndef _WIN32
    //char const * relName;
#endif
    fsl_size_t nName = 0;
    int isDeleted;
    int64_t currentSize;
    int64_t origSize;
    int changed, oldChanged;
    //int isExe;
    fsl_time_t oldMtime, currentMtime;
#if !defined(_WIN32)
    int origPerm;
    int currentPerm;
#endif
    id = fsl_stmt_g_id(&q, 0);
    assert(id>0);
    zName = fsl_stmt_g_text(&q, 1, &nName);
    rid = fsl_stmt_g_id(&q, 2);
    isDeleted = fsl_stmt_g_int32(&q, 3);
    oldChanged = changed = fsl_stmt_g_int32(&q, 4);
    origSize = fsl_stmt_g_int64(&q, 6);
    oldMtime = (fsl_time_t)fsl_stmt_g_int64(&q, 7);
    //isExe = fsl_stmt_g_int32(&q, 8);
    rc = fsl_cx_stat( f, false, zName, &fst );
    currentSize = rc ? -1 : (int64_t)fst.size;
    currentMtime = rc ? 0 : fst.mtime;
    if(rc){
      fsl_cx_err_reset(f);
      rc = 0;
    }
#if !defined(_WIN32)
    //relName = zName + rootLen;
    origPerm = fsl_stmt_g_int32(&q, 10);
    currentPerm = (FSL_FSTAT_PERM_EXE==fst.perm
                   ? FSL_FILE_PERM_EXE
                   : FSL_FILE_PERM_REGULAR)
                   /*(FSL_FSTAT_TYPE_LINK==fst.type
                      ? FSL_FILE_PERM_LINK
                      : FSL_FILE_PERM_REGULAR)*/
      /* ^^^ FIXME: this isn't right for symlinks. For those we have
         to treat them as FSL_FILE_PERM_LINK when the repo has symlink
         support enabled, else FSL_FILE_PERM_REGULAR. That's to fix
         if/when we ever support SCM'd symlinks in the library. */
      ;
#endif
    if(!changed && (isDeleted || !rid)){
      /* ADD and REMOVE operations always change the file */
      changed = FSL_VFILE_CHANGE_MOD;
    }
    else if( currentSize>=0
             && !(FSL_FSTAT_TYPE_FILE==fst.type
                  || FSL_FSTAT_TYPE_LINK==fst.type)){
      if( FSL_VFILE_CKSIG_ENOTFILE & cksigFlags ){
        rc = fsl_cx_err_set(f, FSL_RC_TYPE,
                            "Not an ordinary file or symlink: %s",
                            zName);
        goto end;
      }
      changed = FSL_VFILE_CHANGE_MOD;
    } 
    if(origSize!=currentSize){
      changed = FSL_VFILE_CHANGE_MOD;
      /* A file size change is definitive - the file has changed. No
         need to check the mtime or hash */
    }else if( changed==FSL_VFILE_CHANGE_MOD && rid!=0 && !isDeleted ){
      /* File is believed to have changed but it is the same size.
         Double check that it really has changed by looking at its
         content. */
      fsl_size_t nUuid = 0;
      char const * uuid;
      fsl_buffer_reuse(fileCksum);
      assert( origSize==currentSize );
      uuid = fsl_stmt_g_text(&q, 5, &nUuid);
      assert(uuid && fsl_is_uuid_len((int)nUuid));
      rc = fsl_vfile_recheck_file_hash(f, zName, (int)nUuid, fileCksum);
      if(rc) goto end;
      assert(fsl_is_uuid_len((int)fileCksum->used));
      if( 0 == fsl_uuidcmp(fsl_buffer_cstr(fileCksum), uuid) ){
        changed = 0;
      }
    }else if( (changed==FSL_VFILE_CHANGE_NONE
               || changed==FSL_VFILE_CHANGE_MERGE_MOD
               || changed==FSL_VFILE_CHANGE_INTEGRATE_MOD)
              && (!useMtime || currentMtime!=oldMtime) ){
      /* For files that were formerly believed to be unchanged or that
         were changed by merging, if their mtime changes, or
         unconditionally if FSL_VFILE_CKSIG_SETMTIME is used, check to
         see if they have been edited by looking at their hash sum */
      fsl_size_t nUuid = 0;
      char const * uuid;
      assert( origSize==currentSize );
      uuid = fsl_stmt_g_text(&q, 5, &nUuid);
      assert(uuid && fsl_is_uuid_len((int)nUuid));
      fsl_buffer_reuse(fileCksum);
      rc = fsl_vfile_recheck_file_hash(f, zName, nUuid, fileCksum);
      if(rc) goto end;
      assert(fsl_is_uuid_len((int)fileCksum->used));
      if( fsl_uuidcmp(fsl_buffer_cstr(fileCksum), uuid) ){
        changed = FSL_VFILE_CHANGE_MOD;
      }
      /* MARKER(("SHA compare says %d: %s\n", changed, zName)); */
    }
    if( (cksigFlags & FSL_VFILE_CKSIG_SETMTIME)
        && (changed==FSL_VFILE_CHANGE_NONE
            || changed==FSL_VFILE_CHANGE_MERGE_MOD
            || changed==FSL_VFILE_CHANGE_INTEGRATE_MOD) ){
      fsl_time_t desiredMtime = 0;
      if( 0==fsl_mtime_of_manifest_file(f, vid, rid, &desiredMtime)){
        if( currentMtime != desiredMtime ){
          fsl_file_mtime_set(zName, desiredMtime);
          currentMtime = fsl_file_mtime(zName);
        }
      }
    }
    /* Check for perms differences. */
#if !defined(_WIN32)
    if( origPerm!=FSL_FILE_PERM_LINK && currentPerm==FSL_FILE_PERM_LINK ){
       /* Changing to a symlink takes priority over all other change types. */
       changed = FSL_VFILE_CHANGE_BECAME_SYMLINK;
    }else if( changed==0
              || changed==FSL_VFILE_CHANGE_IS_EXEC
              || changed==FSL_VFILE_CHANGE_BECAME_SYMLINK
              || changed==FSL_VFILE_CHANGE_NOT_EXEC
              || changed==FSL_VFILE_CHANGE_NOT_SYMLINK ){
       /* Confirm metadata change types. */
      if( origPerm==currentPerm ){
        changed = 0;
      }else if( currentPerm==FSL_FILE_PERM_EXE ){
        changed = FSL_VFILE_CHANGE_IS_EXEC;
      }else if( origPerm==FSL_FILE_PERM_EXE ){
        changed = FSL_VFILE_CHANGE_NOT_EXEC;
      }else if( origPerm==FSL_FILE_PERM_LINK ){
        changed = FSL_VFILE_CHANGE_NOT_SYMLINK;
      }
    }
#endif
    if( currentMtime!=oldMtime || changed!=oldChanged ){
      if(!stUpdate){
        rc = fsl_db_prepare_cached(db, &stUpdate,
                                   "UPDATE vfile SET "
                                   "mtime=?1, chnged=?2 "
                                   "WHERE id=?3 "
                                   "/*%s()*/",__func__);
        if(rc) goto end;
      }else{
        fsl_stmt_reset(stUpdate);
      }
      fsl_stmt_bind_int64(stUpdate, 1, currentMtime);
      fsl_stmt_bind_int32(stUpdate, 2, changed);
      fsl_stmt_bind_id(stUpdate, 3, id);
      rc = fsl_stmt_step(stUpdate);
      if(FSL_RC_STEP_DONE!=rc) goto end;
      rc = 0;
      /* MARKER(("UPDATED vfile.(mtime,chnged) for: %s\n", zName)); */
    }
  }/*while(step)*/

#if 0
  MARKER(("changed/deleted vfile contents post vfile scan:\n"));
  fsl_db_each( fsl_cx_db_ckout(f), fsl_stmt_each_f_dump, NULL,
               "SELECT vf.id, substr(b.uuid,0,8) hash, chnged, "
               "deleted, vf.pathname "
               "FROM vfile vf LEFT JOIN blob b "
               "ON b.rid=vf.rid "
               "WHERE vf.vid=%"FSL_ID_T_PFMT" "
               "AND (chnged<>0 OR pathname<>origname OR deleted<>0)"
               "ORDER BY vf.id", vid);
#endif
  end:
  fsl_cx_scratchpad_yield(f, fileCksum);
  if(!rc && (cksigFlags & FSL_VFILE_CKSIG_WRITE_CKOUT_VERSION)
     && (f->ckout.rid != vid)){
    rc = fsl_ckout_version_write(f, vid, 0);
  }else if(rc){
    rc = fsl_cx_uplift_db_error2(f, db, rc);
  }
  if(rc) {
    fsl_db_transaction_rollback(db);
  }else{
    rc = fsl_db_transaction_commit(db);
    if(rc){
      rc = fsl_cx_uplift_db_error2(f, db, rc);
    }
  }
  fsl_stmt_cached_yield(stUpdate);
  fsl_stmt_finalize(&q);
  return rc;
}

int fsl_vfile_to_ckout(fsl_cx * f, fsl_id_t vfileId,
                       int * wasWritten){
  int rc = 0;
  fsl_db * const db = fsl_needs_ckout(f);
  fsl_stmt q = fsl_stmt_empty;
  int counter = 0;
  fsl_buffer content = fsl_buffer_empty;
  char const * sql;
  fsl_id_t qArg;
  fsl_fstat * const fst = &f->cache.fstat;
  if(!db) return FSL_RC_NOT_A_CKOUT;
  assert(f->ckout.rid);
  if(vfileId){
    sql = "SELECT v.id, "
      "%Q || v.pathname, "
      "v.mrid, "
      "v.isexe, v.islink, b.uuid, b.size "
      "FROM vfile v, blob b"
      " WHERE v.id=%" FSL_ID_T_PFMT
      " AND v.mrid>0 "
      " AND v.mrid=b.rid /*%s()*/";
    qArg = vfileId;
  }else{
    sql = "SELECT v.id, "
      "%Q || v.pathname, "
      "v.mrid, "
      "v.isexe, v.islink, b.uuid, b.size "
      "FROM vfile v, blob b"
      " WHERE v.vid=%" FSL_ID_T_PFMT
      " AND v.mrid>0 "
      " AND v.mrid=b.rid /*%s()*/";
    qArg = f->ckout.rid;
  }
#undef VFILE_NAMEPART
  assert(qArg>=0);
  rc = fsl_db_prepare(db, &q, sql, f->ckout.dir, qArg, __func__);
  if(rc){
    rc = fsl_cx_uplift_db_error2(f, db, rc);
    goto end;
  }
  while(FSL_RC_STEP_ROW==(rc = fsl_stmt_step(&q))){
    //fsl_id_t const id = fsl_stmt_g_id(&q, 0);
    fsl_id_t const rid = fsl_stmt_g_id(&q, 2);
    int32_t const isExe = fsl_stmt_g_int32(&q, 3);
    int32_t const isLink = fsl_stmt_g_int32(&q, 4);
    int64_t const sz = fsl_stmt_g_int64(&q, 6);
    fsl_size_t nameLen = 0;
    char const * zName = fsl_stmt_g_text(&q, 1, &nameLen);
    fsl_size_t hashLen = 0;
    char const * zHash = fsl_stmt_g_text(&q, 5, &hashLen);
    char const * zRelName = &zName[f->ckout.dirLen];
    int isMod = 0;
    ++counter;
    assert(nameLen > f->ckout.dirLen);
    rc = fsl_ckout_safe_file_check(f, zName);
    if(rc) break;
    assert(fsl_is_uuid_len(hashLen));
    f->cache.fstat = fsl_fstat_empty;
    rc = fsl_is_locally_modified(f, zName, sz, zHash,
                                 (fsl_int_t)hashLen,
                                 isExe ? FSL_FILE_PERM_EXE :
                                 (isLink
                                  ? FSL_FILE_PERM_LINK
                                  : FSL_FILE_PERM_REGULAR),
                                 &isMod)
      /* that updates f->cache.fstat */;
    if(rc) break;
    else if(FSL_FSTAT_TYPE_DIR==fst->type){
      /* Fossil checks for this but if this happens then
         we have an invalid vfile entry or someone replaced
         a file with a dir. */         
      rc = fsl_cx_err_set(f, FSL_RC_TYPE,
                          "Cannot overwrite a directory: %s",
                          zRelName);
      break;
    }
    else if(!isMod) continue;
    else if((rc=fsl_mkdir_for_file(zName, true))){
      rc = fsl_cx_err_set(f, rc, "mkdir() failed for file: %s",
                          zName);
      break;
    }
    if(FSL_LOCALMOD_LINK & isMod){
      assert(((isLink && FSL_FILE_PERM_LINK!=fst->perm)
              ||(!isLink && FSL_FILE_PERM_LINK==fst->perm))
             && "Expected fsl_is_locally_modified() to set this.");
      rc = fsl_file_unlink(zName);
      if(rc){
        rc = fsl_cx_err_set(f, rc,
                            "Error removing target to replace it: %s",
                            zRelName);
        break;
      }
    }
    if(isLink || (isMod & (FSL_LOCALMOD_NOTFOUND
                           | FSL_LOCALMOD_LINK
                           | FSL_LOCALMOD_CONTENT))){
      /* switched link type, content changed, or was not found in the
         filesystem. */
      rc = fsl_content_get(f, rid, &content);
      if(rc) break;
    }
    if(isLink){
      rc = fsl_ckout_symlink_create(f, zName,
                                    fsl_buffer_cstr(&content));
      if(wasWritten && !rc) *wasWritten = 2;
    }else if(isMod & (FSL_LOCALMOD_NOTFOUND | FSL_LOCALMOD_CONTENT)){
      /* Not found locally or its contents differ. */
      rc = fsl_buffer_to_filename(&content, zName);
      if(rc){
        rc = fsl_cx_err_set(f, rc, "Error writing to file: %s",
                            zRelName);
      }else if(wasWritten){
        *wasWritten = 2;
      }
    }else if(wasWritten && (isMod & FSL_LOCALMOD_PERM)){
      *wasWritten = 1;
    }
    if(rc) break;
    fsl_file_exec_set(zName, !!isExe);
    fsl_buffer_reuse(&content);
  }/*step() loop*/
  switch(rc){
    case FSL_RC_STEP_DONE:
      if(counter){
        rc = 0;
      }else{
        rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                            "No entry found: vfile.id=%" FSL_ID_T_PFMT,
                            vfileId);
      }
      break;
    default: break;
  }    
  end:
  fsl_buffer_clear(&content);
  fsl_stmt_finalize(&q);
  return rc;
}

#undef MARKER
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






















































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted src/vpath.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 contains routines related to working with "paths" through
  Fossil SCM version history.
*/
#include "fossil-scm/fossil-internal.h"
#include "fossil-scm/fossil-vpath.h"
#include <assert.h>

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


static const fsl_vpath_node fsl_vpath_node_empty = {0,0,0,0,0,{0},0};
const fsl_vpath fsl_vpath_empty = fsl_vpath_empty_m;

void fsl_vpath_clear(fsl_vpath *path){
  fsl_vpath_node * p;
  while(path->pAll){
    p = path->pAll;
    path->pAll = p->pAll;
    fsl_free(p);
  }
  fsl_id_bag_clear(&path->seen);
  *path = fsl_vpath_empty;
}

fsl_vpath_node * fsl_vpath_first(fsl_vpath *p){
  return p->pStart;
}

fsl_vpath_node * fsl_vpath_last(fsl_vpath *p){
  return p->pEnd;
}

int fsl_vpath_length(fsl_vpath const * p){
  return p->nStep;
}

fsl_vpath_node * fsl_vpath_next(fsl_vpath_node *p){
  return p->u.pTo;
}

fsl_vpath_node * fsl_vpath_midpoint(fsl_vpath * path){
  if( path->nStep<2 ) return 0;
  else{
    fsl_vpath_node *p;
    int i;
    int const max = path->nStep/2;
    for(p=path->pEnd, i=0; p && i<max; p=p->pFrom, i++){}
    return p;
  }
}


void fsl_vpath_reverse(fsl_vpath * path){
  fsl_vpath_node *p;
  assert( path->pEnd!=0 );
  for(p=path->pEnd; p && p->pFrom; p = p->pFrom){
    p->pFrom->u.pTo = p;
  }
  path->pEnd->u.pTo = 0;
  assert( p==path->pStart );
}

/**
   Adds a new node to path and returns it. Returns 0 on allocation error.
   path must not be 0. rid must be greater than 0. pFrom may be 0. If
   pFrom is not 0 then isParent must be true if pFrom is a parent of
   rid.

   On success, sets the returned node as path->pCurrent, sets its
   pFrom to the given pFrom, and sets rc->u.pPeer to the prior
   path->pCurrent value.
*/
static fsl_vpath_node * fsl_vpath_new_node(fsl_vpath * path, fsl_id_t rid,
                                         fsl_vpath_node * pFrom, bool isParent){
  fsl_vpath_node * rc = 0;
  assert(path);
  assert(rid>0);
  if(0 != fsl_id_bag_insert(&path->seen, rid)) return 0;
  rc = (fsl_vpath_node*)fsl_malloc(sizeof(fsl_vpath_node));
  if(!rc){
    fsl_id_bag_remove(&path->seen, rid);
    return 0;
  }
  *rc = fsl_vpath_node_empty;
  rc->rid = rid;
  rc->fromIsParent = pFrom ? isParent : 0;
  rc->pFrom = pFrom;
  rc->u.pPeer = path->pCurrent;
  path->pCurrent = rc;
  rc->pAll = path->pAll;
  path->pAll = rc;
  return rc;
}

int fsl_vpath_shortest( fsl_cx * f, fsl_vpath * path,
                        fsl_id_t iFrom, fsl_id_t iTo,
                        bool directOnly, bool oneWayOnly ){
  fsl_stmt s = fsl_stmt_empty;
  fsl_db * db = fsl_needs_repo(f);
  int rc = 0;
  fsl_vpath_node * pPrev;
  fsl_vpath_node * p;
  assert(db);
  if(!db) return FSL_RC_NOT_A_REPO;
  else if(iFrom<=0){
    return fsl_cx_err_set(f, FSL_RC_RANGE,
                          "Invalid 'from' RID: %d", (int)iFrom);
  }else if(iTo<=0){
    /*
      Possible TODO: if iTo==0, use... what? Checkout? Tip of current
      checkout branch? Trunk? The multitude of options make it impossible
      to decide :/.      
    */
    return fsl_cx_err_set(f, FSL_RC_RANGE,
                          "Invalid 'to' RID: %d", (int)iTo);
  }

  fsl_vpath_clear(path);
  path->pStart = fsl_vpath_new_node(path, iFrom, 0, 0);
  if(!path->pStart){
    return fsl_cx_err_set(f, FSL_RC_OOM, 0);
  }
  if( iTo == iFrom ){
    path->pEnd = path->pStart;
    return 0;
  }

  if( oneWayOnly ){
    if(directOnly){
      rc = fsl_db_prepare(db, &s,
                          "SELECT cid, 1 FROM plink WHERE pid=?1 AND isprim");
    }else{
      rc = fsl_db_prepare(db, &s,
                          "SELECT cid, 1 FROM plink WHERE pid=?1");
    }
  }else if( directOnly ){
    rc = fsl_db_prepare(db, &s,
                        "SELECT cid, 1 FROM plink WHERE pid=?1 AND isprim "
                        "UNION ALL "
                        "SELECT pid, 0 FROM plink WHERE cid=?1 AND isprim");
  }else{
    rc = fsl_db_prepare(db, &s,
                        "SELECT cid, 1 FROM plink WHERE pid=?1 "
                        "UNION ALL "
                        "SELECT pid, 0 FROM plink WHERE cid=?1");
  }
  if(rc){
    fsl_cx_uplift_db_error(f, db);
    assert(f->error.code);
    goto end;
  }

  while(path->pCurrent){
    ++path->nStep;
    pPrev = path->pCurrent;
    path->pCurrent = 0;
    while( pPrev ){
      rc = fsl_stmt_bind_id(&s, 1, pPrev->rid);
      assert(0==rc);
      while( FSL_RC_STEP_ROW == fsl_stmt_step(&s) ){
        fsl_id_t const cid = fsl_stmt_g_id(&s, 0);
        int const isParent = fsl_stmt_g_int32(&s, 1);
        assert((cid>0) && "fsl_id_bag_find() asserts this.");
        if( fsl_id_bag_contains(&path->seen, cid) ) continue;
        p = fsl_vpath_new_node(path, cid, pPrev, isParent ? 1 : 0);
        if(!p){
          rc = fsl_cx_err_set(f, FSL_RC_OOM, 0);
          goto end;
        }
        if( cid == iTo ){
          fsl_stmt_finalize(&s);
          path->pEnd = p;
          fsl_vpath_reverse( path );
          return 0;
        }
      }
      fsl_stmt_reset(&s);
      pPrev = pPrev->u.pPeer;
    }
  }
  end:
  fsl_stmt_finalize(&s);
  fsl_vpath_clear(path);
  return rc;
}

/*
** A record of a file rename operation.
*/
typedef struct NameChange NameChange;
struct NameChange {
  fsl_id_t origName;        /* Original name of file */
  fsl_id_t curName;         /* Current name of the file */
  fsl_id_t newName;         /* Name of file in next version */
  NameChange *pNext;   /* List of all name changes */
};
const NameChange NameChange_empty =  {0,0,0,0};

int fsl_cx_find_filename_changes(
  fsl_cx * f,
  fsl_id_t iFrom,          /* Ancestor check-in */
  fsl_id_t iTo,            /* Recent check-in */
  bool revOK,              /* OK to move backwards (child->parent) if true */
  uint32_t *pnChng,        /* Number of name changes along the path */
  fsl_id_t **aiChng        /* Name changes */
){
  fsl_vpath_node *p = 0;   /* For looping over path from iFrom to iTo */
  NameChange *pAll = 0;    /* List of all name changes seen so far */
  NameChange *pChng = 0;   /* For looping through the name change list */
  uint32_t nChng = 0;       /* Number of files whose names have changed */
  fsl_id_t *aChng = 0;     /* Two integers per name change */
  fsl_stmt q1 = fsl_stmt_empty; /* Query of name changes */
  fsl_db * const db = fsl_needs_repo(f);
  fsl_vpath path = fsl_vpath_empty;
  int rc = 0;

  if(!db) return FSL_RC_NOT_A_REPO;
  *pnChng = 0;
  *aiChng = 0;
  if(iFrom<=0){
    return fsl_cx_err_set(f, FSL_RC_MISUSE,
                          "Invalid 'from' RID: %" FSL_ID_T_PFMT, iFrom);
  }else if(0==iTo){
    return fsl_cx_err_set(f, FSL_RC_MISUSE,
                          "Invalid 'to' RID: %" FSL_ID_T_PFMT, iTo);
  }
  if( iFrom==iTo ) return 0;
  rc = fsl_vpath_shortest(f, &path, iFrom, iTo, true, !revOK);
  if(rc) goto end;
  else if(!path.pStart){
    goto end;
  }
  fsl_vpath_reverse(&path);
  rc = fsl_db_prepare(db, &q1,
     "SELECT pfnid, fnid FROM mlink"
     " WHERE mid=?1 AND (pfnid>0 OR fid==0)"
     " ORDER BY pfnid"
  );
  if(rc) goto dberr;
  for(p=path.pStart; p; p=p->u.pTo){
    fsl_id_t fnid = 0, pfnid = 0;
    if( !p->fromIsParent && (p->u.pTo==0 || p->u.pTo->fromIsParent) ){
      /* Skip nodes where the parent is not on the path */
      continue;
    }
    fsl_stmt_bind_id(&q1, 1, p->rid);
    while( FSL_RC_STEP_ROW==fsl_stmt_step(&q1) ){
      pfnid = fsl_stmt_g_id(&q1, 0);
      fnid = fsl_stmt_g_id(&q1, 1);
      if( pfnid==0 ){
        pfnid = fnid;
        fnid = 0;
      }
      if( !p->fromIsParent ){
        fsl_id_t const t = fnid;
        fnid = pfnid;
        pfnid = t;
      }
#if 0
      if( zDebug ){
        fossil_print("%s at %d%s %.10z: %d[%z] -> %d[%z]\n",
           zDebug, p->rid, p->fromIsParent ? ">" : "<",
           db_text(0, "SELECT uuid FROM blob WHERE rid=%d", p->rid),
           pfnid,
           db_text(0, "SELECT name FROM filename WHERE fnid=%d", pfnid),
           fnid,
           db_text(0, "SELECT name FROM filename WHERE fnid=%d", fnid));
      }
#endif
      for(pChng=pAll; pChng; pChng=pChng->pNext){
        if( pChng->curName==pfnid ){
          pChng->newName = fnid;
          break;
        }
      }
      if( pChng==0 && fnid>0 ){
        pChng = (NameChange*)fsl_malloc( sizeof(NameChange) );
        if(!pChng){
          rc = FSL_RC_OOM;
          goto end;
        }
        pChng->pNext = pAll;
        pAll = pChng;
        pChng->origName = pfnid;
        pChng->curName = pfnid;
        pChng->newName = fnid;
        ++nChng;
      }
    }
    for(pChng=pAll; pChng; pChng=pChng->pNext){
      pChng->curName = pChng->newName;
    }
    fsl_stmt_reset(&q1);
  }
  if( nChng ){
    /* Count effective changes. */
    uint32_t n;
    for(pChng=pAll, n=0; pChng; pChng=pChng->pNext){
      if( pChng->newName==0 ) continue;
      if( pChng->origName==0 ) continue;
      ++n;
    }
    nChng = n;
  }
  if(nChng){
    uint32_t i;
    aChng = (fsl_id_t*)fsl_malloc( nChng*2*sizeof(fsl_id_t) );
    if(!aChng){
      rc = FSL_RC_OOM;
      goto end;
    }
    for(pChng=pAll, i=0; pChng; pChng=pChng->pNext){
      if( pChng->newName==0 ) continue;
      if( pChng->origName==0 ) continue;
      aChng[i] = pChng->origName;
      aChng[i+1] = pChng->newName;
#if 0
      if( zDebug ){
        fossil_print("%s summary %d[%z] -> %d[%z]\n",
           zDebug,
           aChng[i],
           db_text(0, "SELECT name FROM filename WHERE fnid=%d", aChng[i]),
           aChng[i+1],
           db_text(0, "SELECT name FROM filename WHERE fnid=%d", aChng[i+1]));
      }
#endif
      i += 2;
    }
    assert(nChng==i/2);
    *pnChng = i/2;
    *aiChng = aChng;
    while( pAll ){
      pChng = pAll;
      pAll = pAll->pNext;
      fsl_free(pChng);
    }
  }else{
    *pnChng = 0;
    *aiChng = 0;
  }
  end:
  fsl_stmt_finalize(&q1);
  if(rc){
    assert(!aChng);
  }
  fsl_vpath_clear(&path);
  return rc;
  dberr:
  assert(rc);
  rc = fsl_cx_uplift_db_error2(f, db, rc);
  goto end;
}


#undef MARKER
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










































































































































































































































































































































































































































































































































































































































































































































































Deleted src/wiki.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  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 implements wiki-related parts of the library.
*/
#include "fossil-scm/fossil-internal.h"
#include <assert.h>

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


int fsl_wiki_names_get( fsl_cx * f, fsl_list * tgt ){
  fsl_db * db = fsl_needs_repo(f);
  if(!f || !tgt) return FSL_RC_MISUSE;
  else if(!db) return FSL_RC_NOT_A_REPO;
  else {
    int rc = fsl_db_select_slist( db, tgt,
                                  "SELECT substr(tagname,6) AS name "
                                  "FROM tag "
                                  "WHERE tagname GLOB 'wiki-*' "
                                  "ORDER BY lower(name)");
    if(rc && db->error.code && !f->error.code){
      fsl_cx_uplift_db_error(f, db);
    }
    return rc;
  }
}

int fsl_wiki_latest_rid( fsl_cx * f, char const * pageName, fsl_id_t * rid ){
  fsl_db * db = f ? fsl_needs_repo(f) : NULL;
  if(!f || !pageName) return FSL_RC_MISUSE;
  else if(!*pageName) return FSL_RC_RANGE;
  else if(!db) return FSL_RC_NOT_A_REPO;
  else return fsl_db_get_id(db, rid,
                            "SELECT x.rid FROM tag t, tagxref x "
                            "WHERE x.tagid=t.tagid "
                            "AND t.tagname='wiki-%q' "
                            "ORDER BY mtime DESC LIMIT 1",
                            pageName);
}

bool fsl_wiki_page_exists(fsl_cx * f, char const * pageName){
  fsl_id_t rid = 0;
  return (0==fsl_wiki_latest_rid(f, pageName, &rid))
    && (rid>0);
}

int fsl_wiki_load_latest( fsl_cx * f, char const * pageName, fsl_deck * d ){
  fsl_db * db = f ? fsl_needs_repo(f) : NULL;
  if(!f || !pageName || !d) return FSL_RC_MISUSE;
  else if(!*pageName) return FSL_RC_RANGE;
  else if(!db) return FSL_RC_NOT_A_REPO;
  else{
    fsl_id_t rid = 0;
    int rc = fsl_wiki_latest_rid(f, pageName, &rid);
    if(rc) return rc;
    else if(0==rid) return FSL_RC_NOT_FOUND;
    return fsl_deck_load_rid( f, d, rid, FSL_SATYPE_WIKI);
  }
}

int fsl_wiki_foreach_page( fsl_cx * f, fsl_deck_visitor_f cb, void * state ){
  fsl_db * db = f ? fsl_needs_repo(f) : NULL;
  if(!f || !cb) return FSL_RC_MISUSE;
  else if(!db) return FSL_RC_NOT_A_REPO;
  else{
    fsl_stmt st = fsl_stmt_empty;
    fsl_stmt names = fsl_stmt_empty;
    int rc;
    char doBreak = 0;
    rc = fsl_db_prepare(db, &names,
                        "SELECT substr(tagname,6) AS name "
                        "FROM tag "
                        "WHERE tagname GLOB 'wiki-*' "
                        "ORDER BY lower(name)");
    if(rc) return rc;
    while( !doBreak && !rc
           && (FSL_RC_STEP_ROW==fsl_stmt_step(&names))){
      fsl_size_t nameLen = 0;
      char const * pageName = fsl_stmt_g_text(&names, 0, &nameLen);
      if(!st.stmt){
        rc = fsl_db_prepare(db, &st,
                            "SELECT x.rid AS mrid FROM tag t, tagxref x "
                            "WHERE x.tagid=t.tagid "
                            "AND t.tagname='wiki-'||? "
                            "ORDER BY mtime DESC LIMIT 1");
        if(rc) goto end;
      }
      rc = fsl_stmt_bind_text(&st, 1, pageName, (fsl_int_t)nameLen, 0);
      if(rc) break;
      rc = fsl_stmt_step(&st);
      assert(FSL_RC_STEP_ROW==rc);
      if(FSL_RC_STEP_ROW==rc){
        fsl_deck d = fsl_deck_empty;
        fsl_id_t rid = fsl_stmt_g_id(&st, 0);
        rc = fsl_deck_load_rid( f, &d, rid, FSL_SATYPE_WIKI);
        if(!rc){
          rc = cb(f, &d, state);
          if(FSL_RC_BREAK==rc){
            rc = 0;
            doBreak = 1;
          }
        }
        fsl_deck_finalize(&d);
      }
      fsl_stmt_reset(&st);
    }
    end:
    fsl_stmt_finalize(&st);
    fsl_stmt_finalize(&names);
    return rc;
  }
}

int fsl_wiki_save(fsl_cx * f, char const * pageName,
                  fsl_buffer const * b,
                  char const * userName,
                  char const * mimeType,
                  fsl_wiki_save_mode_t createPolicy ){
  fsl_db * db = f ? fsl_needs_repo(f) : NULL;
  if(!f || !pageName || !b) return FSL_RC_MISUSE;
  else if(!*pageName) return FSL_RC_RANGE;
  else if(!db) return FSL_RC_NOT_A_REPO;
  else{
    fsl_deck d = fsl_deck_empty;
    fsl_id_t parentRid = 0;
    int rc = fsl_wiki_latest_rid(f, pageName, &parentRid);
    double mtime;
    if(rc) return rc;
    else if((FSL_WIKI_SAVE_MODE_UPDATE==createPolicy)
            && !parentRid){
      return fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                            "No such wiki page: %s",
                            pageName);
    }
    else if((FSL_WIKI_SAVE_MODE_CREATE==createPolicy)
            && (parentRid>0)){
      return fsl_cx_err_set(f, FSL_RC_ALREADY_EXISTS,
                            "Wiki page already exists: %s",
                            pageName);
    }
    mtime = fsl_db_julian_now(db);
    fsl_deck_init(f, &d, FSL_SATYPE_WIKI);
    rc = fsl_deck_D_set(&d, mtime);
    assert(!rc);
    rc = fsl_deck_L_set(&d, pageName, -1);
    if(!rc && mimeType && *mimeType){
      rc = fsl_deck_N_set(&d, mimeType, -1);
    }
    if( !rc && parentRid ){
      char * zUuid = fsl_rid_to_uuid(f, parentRid);
      if(!zUuid){
        rc = FSL_RC_OOM;
      }else{
        rc = fsl_deck_P_add(&d,zUuid);
        fsl_free(zUuid);
      }
    }
    if(rc) goto end;
    {
      char * u = NULL;
      if(!userName) userName = fsl_cx_user_get(f);
      if(!userName){
        u = fsl_guess_user_name();
        if(!u) rc = FSL_RC_OOM;
      }
      if(!rc) rc = fsl_deck_U_set(&d, u ? u : userName);
      if(u) fsl_free(u);
      if(rc) goto end;
    }
    rc = fsl_deck_W_set(&d, fsl_buffer_cstr(b), (fsl_int_t)b->used);
#if 0
    fsl_deck_output(f, &d, fsl_output_f_FILE, stdout);
#endif
    if(!rc) rc = fsl_deck_save(&d, 0);
    end:
    fsl_deck_finalize(&d);
    return rc;
  }
}

#undef MARKER
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










































































































































































































































































































































































































Deleted src/zip.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
** Copyright (c) 2017 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*/
/**
   This copy has been modified slightly, and expanded, for use
   with the libfossil project.
*/
#include "fossil-scm/fossil-internal.h"
#include <assert.h>
#include <zlib.h>
#include <stdlib.h> /* atoi() and friends */
#include <memory.h> /* memset() */

#define MARKER(pfexp)                                               \
  do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__);   \
    printf pfexp;                                                   \
  } while(0)


/*
   Write a 16- or 32-bit integer as little-endian into the given buffer.
*/
static void fzip_put16(char *z, int v){
  z[0] = v & 0xff;
  z[1] = (v>>8) & 0xff;
}
static void fzip_put32(char *z, int v){
  z[0] = v & 0xff;
  z[1] = (v>>8) & 0xff;
  z[2] = (v>>16) & 0xff;
  z[3] = (v>>24) & 0xff;
}

/**
    Set the date and time values from an ISO8601 date string.
 */
static void fzip_timestamp_from_str(fsl_zip_writer *z, const char *zDate){
  int y, m, d;
  int H, M, S;

  y = atoi(zDate);
  m = atoi(&zDate[5]);
  d = atoi(&zDate[8]);
  H = atoi(&zDate[11]);
  M = atoi(&zDate[14]);
  S = atoi(&zDate[17]);
  z->dosTime = (H<<11) + (M<<5) + (S>>1);
  z->dosDate = ((y-1980)<<9) + (m<<5) + d;
}

fsl_buffer const * fsl_zip_body( fsl_zip_writer const * z ){
  return z ? &z->body : NULL;
}

void fsl_zip_timestamp_set_julian(fsl_zip_writer *z, double rDate){
  char buf[20] = {0};
  fsl_julian_to_iso8601(rDate, buf, 0);
  fzip_timestamp_from_str(z, buf);
  z->unixTime = (fsl_time_t)((rDate - 2440587.5)*86400.0);
}

void fsl_zip_timestamp_set_unix(fsl_zip_writer *z, fsl_time_t epochTime){
  char buf[20] = {0};
  fsl_julian_to_iso8601(fsl_unix_to_julian(epochTime), buf, 0);
  fzip_timestamp_from_str(z, buf);
  z->unixTime = epochTime;
}


/**
    Adds all directories for the given file to the zip if they are not
    in there already. Returns 0 on success, non-0 on error (namely
    OOM).
 */
static int fzip_mkdir(fsl_zip_writer * z, char const *zName);

/**
    Adds a file entry to zw's zip output. zName is the virtual name of
    the file or directory. If pSrc is NULL then it is assumed that we
    are creating a directory, otherwise the zip's entry is populated
    from pSrc. mPerms specify the fossil-specific permission flags
    from the fsl_fileperm_e enum. If doMkDirs is true then fzip_mkdir()
    is called to create the directory entries for zName, otherwise
    they are not.
 */
static int fzip_file_add(fsl_zip_writer *zw, char const * zName,
                         fsl_buffer const * pSrc, int mPerm,
                         char doMkDirs){
  int rc = 0;
  z_stream stream;
  fsl_size_t nameLen;
  int toOut = 0;
  int iStart;
  int iCRC = 0;
  int nByte = 0;
  int nByteCompr = 0;
  int nBlob;                 /* Size of the blob */
  int iMethod;               /* Compression method. */
  int iMode = 0644;          /* Access permissions */
  char *z;
  char zHdr[30];
  char zExTime[13];
  char zBuf[100];
  char zOutBuf[/*historical: 100000*/ 1024 * 16];

  /* Fill in as much of the header as we know.
  */
  nBlob = pSrc ? (int)pSrc->used : 0;
  if( pSrc ){ /* a file entry */
    iMethod = pSrc->used ? 8 : 0 /* don't compress 0-byte files */;
    switch( mPerm ){
      case FSL_FILE_PERM_LINK:  iMode = 0120755;   break;
      case FSL_FILE_PERM_EXE:   iMode = 0100755;   break;
      default:         iMode = 0100644;   break;
    }
  }else{ /* a directory entry */
    iMethod = 0;
    iMode = 040755;
  }
  if(doMkDirs){
    rc = fzip_mkdir(zw, zName)
      /* This causes an extraneous run of fzip_mkdir(),
         but it is harmless other than the waste of search
         time */;
    if(rc) return rc;
  }

  if(zw->rootDir){
    zw->scratch.used = 0;
    rc = fsl_buffer_appendf(&zw->scratch, "%s%s", zw->rootDir, zName);
    if(rc){
      assert(FSL_RC_OOM==rc);
      return rc;
    }
    zName = fsl_buffer_cstr(&zw->scratch);
  }

  nameLen = fsl_strlen(zName);
  memset(zHdr, 0, sizeof(zHdr));
  fzip_put32(&zHdr[0], 0x04034b50);
  fzip_put16(&zHdr[4], 0x000a);
  fzip_put16(&zHdr[6], 0x0800);
  fzip_put16(&zHdr[8], iMethod);
  fzip_put16(&zHdr[10], zw->dosTime);
  fzip_put16(&zHdr[12], zw->dosDate);
  fzip_put16(&zHdr[26], nameLen);
  fzip_put16(&zHdr[28], 13);

  fzip_put16(&zExTime[0], 0x5455);
  fzip_put16(&zExTime[2], 9);
  zExTime[4] = 3;
  fzip_put32(&zExTime[5], zw->unixTime);
  fzip_put32(&zExTime[9], zw->unixTime);


  /* Write the header and filename.
  */
  iStart = (int)zw->body.used;
  fsl_buffer_append(&zw->body, zHdr, 30);
  fsl_buffer_append(&zw->body, zName, nameLen);
  fsl_buffer_append(&zw->body, zExTime, 13);

  if( nBlob>0 ){
    /* Write the compressed file.  Compute the CRC as we progress.
    */
    stream.zalloc = (alloc_func)0;
    stream.zfree = (free_func)0;
    stream.opaque = 0;
    stream.avail_in = pSrc->used;
    stream.next_in = /* (unsigned char*) */pSrc->mem;
    stream.avail_out = sizeof(zOutBuf);
    stream.next_out = (unsigned char*)zOutBuf;
    deflateInit2(&stream, 9, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY);
    iCRC = crc32(0, stream.next_in, stream.avail_in);
    while( stream.avail_in>0 ){
      deflate(&stream, 0);
      toOut = sizeof(zOutBuf) - stream.avail_out;
      fsl_buffer_append(&zw->body, zOutBuf, toOut);
      stream.avail_out = sizeof(zOutBuf);
      stream.next_out = (unsigned char*)zOutBuf;
    }
    do{
      stream.avail_out = sizeof(zOutBuf);
      stream.next_out = (unsigned char*)zOutBuf;
      deflate(&stream, Z_FINISH);
      toOut = sizeof(zOutBuf) - stream.avail_out;
      fsl_buffer_append(&zw->body, zOutBuf, toOut);
    }while( stream.avail_out==0 );
    nByte = stream.total_in;
    nByteCompr = stream.total_out;
    deflateEnd(&stream);

    /* Go back and write the header, now that we know the compressed file size.
    */
    z = (char *)zw->body.mem + iStart/* &blob_buffer(&body)[iStart] */;
    fzip_put32(&z[14], iCRC);
    fzip_put32(&z[18], nByteCompr);
    fzip_put32(&z[22], nByte);
  }

  /* Make an entry in the tables of contents
  */
  memset(zBuf, 0, sizeof(zBuf));
  fzip_put32(&zBuf[0], 0x02014b50);
  fzip_put16(&zBuf[4], 0x0317);
  fzip_put16(&zBuf[6], 0x000a);
  fzip_put16(&zBuf[8], 0x0800);
  fzip_put16(&zBuf[10], iMethod);
  fzip_put16(&zBuf[12], zw->dosTime);
  fzip_put16(&zBuf[14], zw->dosDate);
  fzip_put32(&zBuf[16], iCRC);
  fzip_put32(&zBuf[20], nByteCompr);
  fzip_put32(&zBuf[24], nByte);
  fzip_put16(&zBuf[28], nameLen);
  fzip_put16(&zBuf[30], 9);
  fzip_put16(&zBuf[32], 0);
  fzip_put16(&zBuf[34], 0);
  fzip_put16(&zBuf[36], 0);
  fzip_put32(&zBuf[38], ((unsigned)iMode)<<16);
  fzip_put32(&zBuf[42], iStart);
  fsl_buffer_append(&zw->toc, zBuf, 46);
  fsl_buffer_append(&zw->toc, zName, nameLen);
  fzip_put16(&zExTime[2], 5);
  fsl_buffer_append(&zw->toc, zExTime, 9);
  ++zw->entryCount;

  return rc;
}

int fzip_mkdir(fsl_zip_writer * z, char const *zName){
  fsl_size_t i;
  fsl_size_t j;
  int rc = 0;
  char const * dirName;
  fsl_size_t nDir = z->dirs.used;
  for(i=0; zName[i]; i++){
    if( zName[i]=='/' ){
      while(zName[i+1]=='/') ++i /* Skip extra slashes */;
      for(j=0; j<nDir; j++){
        /* See if we know this dir already... */
        dirName = (char const *)z->dirs.list[j];
        if( fsl_strncmp(zName, dirName, i)==0 ) break;
      }
      if( j>=nDir ){
        char * cp = fsl_strndup(zName, (fsl_int_t)i+1);
        rc = cp ? fsl_list_append(&z->dirs, cp) : FSL_RC_OOM;
        if(cp && rc){
          fsl_free(cp);
        }else{
          rc = fzip_file_add(z, cp, NULL, 0, 0);
        }
      }
    }
  }
  return rc;
}

int fsl_zip_file_add(fsl_zip_writer *z, char const * zName,
                     fsl_buffer const * pSrc, int mPerm){
  return fzip_file_add(z, zName, pSrc, mPerm, 1);
}

int fsl_zip_root_set(fsl_zip_writer * z, char const * zRoot ){
  if(!z) return FSL_RC_MISUSE;
  else if(zRoot && *zRoot && fsl_is_absolute_path(zRoot)){
    return FSL_RC_RANGE;
  }else{
    fsl_free(z->rootDir);
    z->rootDir = NULL;
    if(zRoot && *zRoot){
      /*
        Problem: we have to mkdir zRoot before we assign z->rootDir to
        avoid an interesting ROOT/ROOT dir entry on an otherwise empty
        ZIP. We create the dirs here, instead of during the first file
        insertion (after z->rootDir is set), to work around that.
      */
      char * cp;
      fsl_size_t n = fsl_strlen(zRoot);
      if('/'==zRoot[n-1]){
        /* Keep the slash */
        cp = fsl_strndup(zRoot, (fsl_int_t)n);
      }else{
        /* Add a slash to our copy... */
        cp = (char *)fsl_malloc(n+2);
        if(cp){
          memcpy( cp, zRoot, n );
          cp[n] = '/';
          cp[n+1] = 0;
          ++n;
        }
      }
      if(!cp) return FSL_RC_OOM;
      else{
        int rc;
        n = fsl_file_simplify_name(cp, (fsl_int_t)n, 1);
        assert(n);
        assert('/'==cp[n-1]);
        cp[n-1] = 0;
        rc = fsl_is_simple_pathname(cp, 1);
        cp[n-1] = '/';
        rc = rc
          ? fzip_mkdir(z, cp)
          : FSL_RC_RANGE;
        z->rootDir = cp /* transfer ownership on error as well and let
                           normal downstream clean it up. */;
        return rc;
      }
    }
    return 0;
  }
}

static void fsl_zip_finalize_impl(fsl_zip_writer * z, char alsoBody){
  if(z){
    fsl_buffer_clear(&z->toc);
    fsl_buffer_clear(&z->scratch);
    fsl_list_visit_free(&z->dirs, 1);
    assert(NULL==z->dirs.list);
    fsl_free(z->rootDir);
    if(alsoBody){
      fsl_buffer_clear(&z->body);
      *z = fsl_zip_writer_empty;
    }else{
      fsl_buffer cp = z->body;
      *z = fsl_zip_writer_empty;
      z->body = cp;
    }
  }
}

void fsl_zip_finalize(fsl_zip_writer * z){
  fsl_zip_finalize_impl(z, 1);
}


int fsl_zip_end( fsl_zip_writer * z ){
  int rc;
  fsl_int_t iTocStart;
  fsl_int_t iTocEnd;
  char zBuf[30];

  iTocStart = (fsl_int_t)z->body.used;
  rc = fsl_buffer_append(&z->body, z->toc.mem, z->toc.used);
  if(rc) return rc;
  fsl_buffer_clear(&z->toc);
  iTocEnd = (fsl_int_t)z->body.used;

  memset(zBuf, 0, sizeof(zBuf));
  fzip_put32(&zBuf[0], 0x06054b50);
  fzip_put16(&zBuf[4], 0);
  fzip_put16(&zBuf[6], 0);
  fzip_put16(&zBuf[8], (int)z->entryCount);
  fzip_put16(&zBuf[10], (int)z->entryCount);
  fzip_put32(&zBuf[12], iTocEnd - iTocStart);
  fzip_put32(&zBuf[16], iTocStart);
  fzip_put16(&zBuf[20], 0);

  rc = fsl_buffer_append(&z->body, zBuf, 22);
  fsl_zip_finalize_impl(z, 0);
  assert(z->body.used);
  return rc;
}

int fsl_zip_end_take( fsl_zip_writer * z, fsl_buffer * dest ){
  if(!z) return FSL_RC_MISUSE;
  else{
    int rc;
    if(!dest){
      rc = FSL_RC_MISUSE;
    }else{
      rc = fsl_zip_end(z);
      if(!rc){
        fsl_buffer_swap( &z->body, dest );
      }
    }
    fsl_zip_finalize( z );
    return rc;
  }
}

int fsl_zip_end_to_filename( fsl_zip_writer * z, char const * filename ){
  if(!z) return FSL_RC_MISUSE;
  else{
    int rc;
    if(!filename || !*filename){
      rc = FSL_RC_MISUSE;
    }else{
      rc = fsl_zip_end(z);
      if(!rc){
        rc = fsl_buffer_to_filename(&z->body, filename);
      }
    }
    fsl_zip_finalize( z );
    return rc;
  }
}



struct ZipState{
  fsl_cx * f;
  fsl_id_t vid;
  fsl_card_F_visitor_f progress;
  void * progressState;
  fsl_zip_writer z;
  fsl_buffer cbuf;
};
typedef struct ZipState ZipState;
static const ZipState ZipState_empty = {
NULL, 0, NULL, NULL,
fsl_zip_writer_empty_m,
fsl_buffer_empty_m
};

static int fsl_card_F_visitor_zip(fsl_card_F const * fc,
                                   void * state){
  ZipState * zs = (ZipState *)state;
  fsl_id_t frid;
  int rc = 0;
  if(!fc->uuid) return 0 /* file was removed in this (delta) manifest */;
  else if(zs->progress){
    rc = (*zs->progress)(fc, zs->progressState);
    if(rc) return rc;
  }else if(FSL_FILE_PERM_LINK == fc->perm){
    return fsl_cx_err_set(zs->f, FSL_RC_NYI,
                          "Symlinks are not yet supported "
                          "in ZIP output.");
  }
  frid = fsl_uuid_to_rid(zs->f, fc->uuid);
  if(frid<0){
    rc = zs->f->error.code;
  }else if(!frid){
      assert(zs->f->error.code);
      rc = zs->f->error.code;
  }else{
    fsl_time_t mTime = 0;
    rc = fsl_mtime_of_manifest_file(zs->f, zs->vid, frid, &mTime);
    if(!rc){
      fsl_zip_timestamp_set_unix(&zs->z, mTime);
      zs->cbuf.used = 0;
      rc = fsl_content_get(zs->f, frid, &zs->cbuf);
      if(!rc){
        rc = fsl_zip_file_add(&zs->z, fc->name, &zs->cbuf,
                              FSL_FILE_PERM_REGULAR);
        if(rc){
          fsl_cx_err_set(zs->f, rc,
                         "Error %s adding file [%s] "
                         "to zip.", fsl_rc_cstr(rc),
                         fc->name);
        }
      }
    }
  }
  return rc;
}


int fsl_repo_zip_sym_to_filename( fsl_cx * f, char const * sym,
                                  char const *  rootDir,
                                  char const * fileName,
                                  fsl_card_F_visitor_f progress,
                                  void * progressState ){
  int rc;
  fsl_deck mf = fsl_deck_empty;
  ZipState zs = ZipState_empty;
  if(!f || !sym || !fileName || !*sym || !*fileName) return FSL_RC_MISUSE;
  else if(!fsl_needs_repo(f)) return FSL_RC_NOT_A_REPO;

  rc = fsl_deck_load_sym( f, &mf, sym, FSL_SATYPE_CHECKIN );
  if(rc) goto end;

  if(rootDir && *rootDir){
    fsl_time_t rootTime = 0;
    rc = fsl_mtime_of_manifest_file(f, mf.rid, 0, &rootTime);
    if(rc) return rc;
    fsl_zip_timestamp_set_unix(&zs.z, rootTime);
    rc = fsl_zip_root_set( &zs.z, rootDir );
    if(rc) goto end;
  }

  rc = fsl_deck_F_rewind(&mf);
  if(rc) goto end;

  zs.f = f;
  zs.vid = mf.rid;
  zs.progress = progress;
  zs.progressState = progressState;
  rc = fsl_deck_F_foreach( &mf, fsl_card_F_visitor_zip, &zs);
  if(!rc){
    if(!zs.z.entryCount){
      if(rootDir && *rootDir){
        rc = fsl_zip_file_add( &zs.z, rootDir, NULL, FSL_FILE_PERM_REGULAR );
      }else{
        rc = fsl_cx_err_set(f, FSL_RC_RANGE, "Cowardly refusing to create "
                            "empty ZIP file for repo version [%.*s].",
                            12, mf.uuid);
      }
      if(rc) goto end;
    }
    rc = fsl_zip_end( &zs.z );
    if(!rc) rc = fsl_buffer_to_filename( fsl_zip_body(&zs.z), fileName );
  }
  end:
  if(rc && !f->error.code){
    fsl_cx_err_set(f, rc, "Error #%d (%s) during ZIP.",
                   rc, fsl_rc_cstr(rc));
  }
  fsl_buffer_clear(&zs.cbuf);
  fsl_zip_finalize(&zs.z);
  fsl_deck_clean(&mf);
  return rc;
}



#undef MARKER
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted tools/c-struct.sh.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/bin/bash

if [[ x = "x$1" ]]; then
    echo "Usage: $0 struct_name"
    exit 1
fi

s=$1
cat <<EOF
/**

*/
struct $s {
  int dummy;
};

/** Convenience typedef. */
typedef struct $s $s;

/** Initialized-with-defaults $s structure, intended for
    const-copy initialization. */
#define ${s}_empty_m {0}

/** Initialized-with-defaults $s structure, intended for
    non-const copy initialization. */
extern const $s ${s}_empty;

// Put this in a C file:
const $s ${s}_empty = ${s}_empty_m;

EOF
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






























































Deleted tools/codecheck-strformat.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
/*
** Copyright (c) 2014 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This program reads Fossil source code files and tries to verify that
** printf-style format strings are correct.
**
** This program implements a compile-time validation step on the Fossil
** source code.  Running this program is entirely optional.  Its role is
** similar to the -Wall compiler switch on gcc, or the scan-build utility
** of clang, or other static analyzers.  The purpose is to try to identify
** problems in the source code at compile-time.  The difference is that this
** static checker is specifically designed for the particular printf formatter
** implementation used by Fossil.
**
** Checks include:
**
**    *  Verify that vararg formatting routines like blob_printf() or
**       db_multi_exec() have the correct number of arguments for their
**       format string.
**
**    *  For routines designed to generate SQL, warn about the use of %s
**       which might allow SQL injection.
*/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>

/*
** Malloc, aborting if it fails.
*/
void *safe_malloc(int nByte){
  void *x = malloc(nByte);
  if( x==0 ){
    fprintf(stderr, "failed to allocate %d bytes\n", nByte);
    exit(1);
  }
  return x;
}
void *safe_realloc(void *pOld, int nByte){
  void *x = realloc(pOld, nByte);
  if( x==0 ){
    fprintf(stderr, "failed to allocate %d bytes\n", nByte);
    exit(1);
  }
  return x;
}

/*
** Read the entire content of the file named zFilename into memory obtained
** from malloc().   Add a zero-terminator to the end.
** Return a pointer to that memory.
*/
static char *read_file(const char *zFilename){
  FILE *in;
  char *z;
  int nByte;
  int got;
  in = fopen(zFilename, "rb");
  if( in==0 ){
    return 0;
  }
  fseek(in, 0, SEEK_END);
  nByte = ftell(in);
  fseek(in, 0, SEEK_SET);
  z = safe_malloc( nByte+1 );
  got = fread(z, 1, nByte, in);
  z[got] = 0;
  fclose(in);
  return z;
}

/*
** When parsing the input file, the following token types are recognized.
*/
#define TK_SPACE      1      /* Whitespace or comments */
#define TK_ID         2      /* An identifier */
#define TK_STR        3      /* A string literal in double-quotes */
#define TK_OTHER      4      /* Any other token */
#define TK_EOF       99      /* End of file */

/*
** Determine the length and type of the token beginning at z[0]
*/
static int token_length(const char *z, int *pType, int *pLN){
  int i;
  if( z[0]==0 ){
    *pType = TK_EOF;
    return 0;
  }
  if( z[0]=='"' || z[0]=='\'' ){
    for(i=1; z[i] && z[i]!=z[0]; i++){
      if( z[i]=='\\' && z[i+1]!=0 ){
        if( z[i+1]=='\n' ) (*pLN)++;
        i++;
      }
    }
    if( z[i]!=0 ) i++;
    *pType = z[0]=='"' ? TK_STR : TK_OTHER;
    return i;
  }
  if( isalnum(z[0]) || z[0]=='_' ){
    for(i=1; isalnum(z[i]) || z[i]=='_'; i++){}
    *pType = isalpha(z[0]) || z[0]=='_' ? TK_ID : TK_OTHER;
    return i;
  }
  if( isspace(z[0]) ){
    if( z[0]=='\n' ) (*pLN)++;
    for(i=1; isspace(z[i]); i++){
      if( z[i]=='\n' ) (*pLN)++;
    }
    *pType = TK_SPACE;
    return i;
  }
  if( z[0]=='/' && z[1]=='*' ){
    for(i=2; z[i] && (z[i]!='*' || z[i+1]!='/'); i++){
      if( z[i]=='\n' ) (*pLN)++;
    }
    if( z[i] ) i += 2;
    *pType = TK_SPACE;
    return i;
  }
  if( z[0]=='/' && z[1]=='/' ){
    for(i=2; z[i] && z[i]!='\n'; i++){}
    if( z[i] ){
      (*pLN)++;
      i++;
    }
    *pType = TK_SPACE;
    return i;
  }
  *pType = TK_OTHER;
  return 1;
}

/*
** Return the next non-whitespace token
*/
const char *next_non_whitespace(const char *z, int *pLen, int *pType){
  int len;
  int eType;
  int ln = 0;
  while( (len = token_length(z, &eType, &ln))>0 && eType==TK_SPACE ){
    z += len;
  }
  *pLen = len;
  *pType = eType;
  return z;
}

/*
** Return index into z[] for the first balanced TK_OTHER token with
** value cValue.
*/
static int distance_to(const char *z, char cVal){
  int len;
  int dist = 0;
  int eType;
  int nNest = 0;
  int ln = 0;
  while( z[0] && (len = token_length(z, &eType, &ln))>0 ){
    if( eType==TK_OTHER ){
      if( z[0]==cVal && nNest==0 ){
        break;
      }else if( z[0]=='(' ){
        nNest++;
      }else if( z[0]==')' ){
        nNest--;
      }
    }
    dist += len;
    z += len;
  }
  return dist;
}

/*
** Return the first non-whitespace characters in z[]
*/
static const char *skip_space(const char *z){
  while( isspace(z[0]) ){ z++; }
  return z;
}

/*
** Return true if the input is a string literal.
*/
static int is_string_lit(const char *z){
  int nu1, nu2;
  z = next_non_whitespace(z, &nu1, &nu2);
  return z[0]=='"';
}

/*
** Return true if the input is an expression of string literals:
**
**      EXPR ? "..." : "..."
*/
static int is_string_expr(const char *z){
  int len = 0, eType;
  const char *zOrig = z;
  len = distance_to(z, '?');
  if( z[len]==0 && skip_space(z)[0]=='(' ){
    z = skip_space(z) + 1;
    len = distance_to(z, '?');
  }
  z += len;
  if( z[0]=='?' ){
    z++;
    z = next_non_whitespace(z, &len, &eType);
    if( eType==TK_STR ){
      z += len;
      z = next_non_whitespace(z, &len, &eType);
      if( eType==TK_OTHER && z[0]==':' ){
        z += len;
        z = next_non_whitespace(z, &len, &eType);
        if( eType==TK_STR ){
          z += len;
          z = next_non_whitespace(z, &len, &eType);
          if( eType==TK_EOF ) return 1;
          if( eType==TK_OTHER && z[0]==')' && skip_space(zOrig)[0]=='(' ){
            z += len;
            z = next_non_whitespace(z, &len, &eType);
            if( eType==TK_EOF ) return 1;
          }
        }
      }
    }
  }
  return 0;
}

/*
** A list of functions that return strings that are safe to insert into
** SQL using %s.
*/
static const char *azSafeFunc[] = {
  "filename_collation",
  "db_name",
  "timeline_utc",
  "leaf_is_closed_sql",
  "timeline_query_for_www",
  "timeline_query_for_tty",
  "blob_sql_text",
  "glob_expr",
  "fossil_all_reserved_names",
  "configure_inop_rhs",
  "db_setting_inop_rhs",
};

/*
** Return true if the input is an argument that is safe to use with %s
** while building an SQL statement.
*/
static int is_s_safe(const char *z){
  int len, eType;
  int i;

  /* A string literal is safe for use with %s */
  if( is_string_lit(z) ) return 1;

  /* Certain functions are guaranteed to return a string that is safe
  ** for use with %s */
  z = next_non_whitespace(z, &len, &eType);
  for(i=0; i<sizeof(azSafeFunc)/sizeof(azSafeFunc[0]); i++){
    if( eType==TK_ID 
     && strncmp(z, azSafeFunc[i], len)==0
     && strlen(azSafeFunc[i])==len
    ){
      return 1;
    }
  }

  /* Expressions of the form:  EXPR ? "..." : "...." can count as
  ** a string literal. */
  if( is_string_expr(z) ) return 1;

  /* If the "safe-for-%s" comment appears in the argument, then
  ** let it through */
  if( strstr(z, "/*safe-for-%s*/")!=0 ) return 1;
    
  return 0;
}

/*
** Processing flags
*/
#define FMT_NO_S   0x00001     /* Do not allow %s substitutions */

/*
** A list of internal Fossil interfaces that take a printf-style format
** string.
*/
struct {
  const char *zFName;    /* Name of the function */
  int iFmtArg;           /* Index of format argument.  Leftmost is 1. */
  unsigned fmtFlags;     /* Processing flags */
} aFmtFunc[] = {
#if 1
  { "fsl_buffer_appendf", 2, FMT_NO_S }
#else
  /* Original list from fossil... */
  { "blob_append_sql",         2, FMT_NO_S },
  { "blob_appendf",            2, 0 },
  { "cgi_panic",               1, 0 },
  { "cgi_redirectf",           1, 0 },
  { "db_blob",                 2, FMT_NO_S },
  { "db_double",               2, FMT_NO_S },
  { "db_err",                  1, 0 },
  { "db_exists",               1, FMT_NO_S },
  { "db_int",                  2, FMT_NO_S },
  { "db_int64",                2, FMT_NO_S },
  { "db_multi_exec",           1, FMT_NO_S },
  { "db_optional_sql",         2, FMT_NO_S },
  { "db_prepare",              2, FMT_NO_S },
  { "db_prepare_ignore_error", 2, FMT_NO_S },
  { "db_static_prepare",       2, FMT_NO_S },
  { "db_text",                 2, FMT_NO_S },
  { "form_begin",              2, 0 },
  { "fossil_error",            2, 0 },
  { "fossil_errorlog",         1, 0 },
  { "fossil_fatal",            1, 0 },
  { "fossil_fatal_recursive",  1, 0 },
  { "fossil_panic",            1, 0 },
  { "fossil_print",            1, 0 },
  { "fossil_trace",            1, 0 },
  { "fossil_warning",          1, 0 },
  { "href",                    1, 0 },
  { "mprintf",                 1, 0 },
  { "socket_set_errmsg",       1, 0 },
  { "ssl_set_errmsg",          1, 0 },
  { "style_header",            1, 0 },
  { "style_set_current_page",  1, 0 },
  { "webpage_error",           1, 0 },
  { "xhref",                   2, 0 },
#endif
};

/*
** Determine if the indentifier zIdent of length nIndent is a Fossil
** internal interface that uses a printf-style argument.  Return zero if not.
** Return the index of the format string if true with the left-most
** argument having an index of 1.
*/
static int isFormatFunc(const char *zIdent, int nIdent, unsigned *pFlags){
  int upr, lwr;
  lwr = 0;
  upr = sizeof(aFmtFunc)/sizeof(aFmtFunc[0]) - 1;
  while( lwr<=upr ){
    unsigned x = (lwr + upr)/2;
    int c = strncmp(zIdent, aFmtFunc[x].zFName, nIdent);
    if( c==0 ){
      if( aFmtFunc[x].zFName[nIdent]==0 ){
        *pFlags = aFmtFunc[x].fmtFlags;
        return aFmtFunc[x].iFmtArg;
      }
      c = -1;
    }
    if( c<0 ){
      upr = x - 1;
    }else{
      lwr = x + 1;
    }
  }
  *pFlags = 0;
  return 0;
}

/*
** Return the expected number of arguments for the format string.
** Return -1 if the value cannot be computed.
**
** For each argument less than nType, store the conversion character
** for that argument in cType[i].
*/
static int formatArgCount(const char *z, int nType, char *cType){
  int nArg = 0;
  int i, k;
  int len;
  int eType;
  int ln = 0;
  while( z[0] ){
    len = token_length(z, &eType, &ln);
    if( eType==TK_STR ){
      for(i=1; i<len-1; i++){
        if( z[i]!='%' ) continue;
        if( z[i+1]=='%' ){ i++; continue; }
        for(k=i+1; k<len && !isalpha(z[k]); k++){
          if( z[k]=='*' || z[k]=='#' ){
            if( nArg<nType ) cType[nArg] = z[k];
            nArg++;
          }
        }
        if( z[k]!='R' ){
          if( nArg<nType ) cType[nArg] = z[k];
          nArg++;
        }
      }
    }
    z += len;
  }
  return nArg;
}

/*
** The function call that begins at zFCall[0] (which is on line lnFCall of the
** original file) is a function that uses a printf-style format string
** on argument number fmtArg.  It has processings flags fmtFlags.  Do
** compile-time checking on this function, output any errors, and return
** the number of errors.
*/
static int checkFormatFunc(
  const char *zFilename, /* Name of the file being processed */
  const char *zFCall,    /* Pointer to start of function call */
  int lnFCall,           /* Line number that holds z[0] */
  int fmtArg,            /* Format string should be this argument */
  int fmtFlags           /* Extra processing flags */
){
  int szFName;
  int eToken;
  int ln = lnFCall;
  int len;
  const char *zStart;
  char *z;
  char *zCopy;
  int nArg = 0;
  const char **azArg = 0;
  int i, k;
  int nErr = 0;
  char *acType;

  szFName = 	token_length(zFCall, &eToken, &ln);
  zStart = next_non_whitespace(zFCall+szFName, &len, &eToken);
  assert( zStart[0]=='(' && len==1 );
  len = distance_to(zStart+1, ')');
  zCopy = safe_malloc( len + 1 );
  memcpy(zCopy, zStart+1, len);
  zCopy[len] = 0;
  azArg = 0;
  nArg = 0;
  z = zCopy;
  while( z[0] ){
    len = distance_to(z, ',');
    azArg = safe_realloc(azArg, (sizeof(azArg[0])+1)*(nArg+1));
    azArg[nArg++] = skip_space(z);
    if( z[len]==0 ) break;
    z[len] = 0;
    for(i=len-1; i>0 && isspace(z[i]); i--){ z[i] = 0; }
    z += len + 1;
  }
  acType = (char*)&azArg[nArg];
  if( fmtArg>nArg ){
    printf("%s:%d: too few arguments to %.*s()\n", 
           zFilename, lnFCall, szFName, zFCall);
    nErr++;
  }else{
    const char *zFmt = azArg[fmtArg-1];
    const char *zOverride = strstr(zFmt, "/*works-like:");
    if( zOverride ) zFmt = zOverride + sizeof("/*works-like:")-1;
    if( !is_string_lit(zFmt) ){
      printf("%s:%d: %.*s() has non-constant format string\n",
             zFilename, lnFCall, szFName, zFCall);
      nErr++;
    }else if( (k = formatArgCount(zFmt, nArg, acType))>=0
             && nArg!=fmtArg+k ){
      printf("%s:%d: too %s arguments to %.*s() "
             "- got %d and expected %d\n",
             zFilename, lnFCall, (nArg<fmtArg+k ? "few" : "many"),
             szFName, zFCall, nArg, fmtArg+k);
      nErr++;
    }else if( fmtFlags & FMT_NO_S ){
      for(i=0; i<nArg && i<k; i++){
        if( (acType[i]=='s' || acType[i]=='z' || acType[i]=='b')
         && !is_s_safe(azArg[fmtArg+i])
        ){
           printf("%s:%d: Argument %d to %.*s() not safe for SQL\n",
             zFilename, lnFCall, i+fmtArg, szFName, zFCall);
           nErr++;
        }
      }
    }
  }
  if( nErr ){
    for(i=0; i<nArg; i++){
      printf("   arg[%d]: %s\n", i, azArg[i]);
    }
  }

  free(azArg);
  free(zCopy);
  return nErr;
}


/*
** Do a design-rule check of format strings for the file named zName
** with content zContent.  Write errors on standard output.  Return
** the number of errors.
*/
static int scan_file(const char *zName, const char *zContent){
  const char *z;
  int ln = 0;
  int szToken;
  int eToken;
  const char *zPrev;
  int ePrev;
  int szPrev;
  int lnPrev;
  int nCurly = 0;
  int x;
  unsigned fmtFlags = 0;
  int nErr = 0;

  if( zContent==0 ){
    printf("cannot read file: %s\n", zName);
    return 1;
  }
  for(z=zContent; z[0]; z += szToken){
    szToken = token_length(z, &eToken, &ln);
    if( eToken==TK_SPACE ) continue;
    if( eToken==TK_OTHER ){
      if( z[0]=='{' ){
        nCurly++;
      }else if( z[0]=='}' ){
        nCurly--;
      }else if( nCurly>0 && z[0]=='(' && ePrev==TK_ID
            && (x = isFormatFunc(zPrev,szPrev,&fmtFlags))>0 ){
        nErr += checkFormatFunc(zName, zPrev, lnPrev, x, fmtFlags);
      }
    }    
    zPrev = z;
    ePrev = eToken;
    szPrev = szToken;
    lnPrev = ln;
  }
  return nErr;
}

/*
** Check for format-string design rule violations on all files listed
** on the command-line.
*/
int main(int argc, char **argv){
  int i;
  int nErr = 0;
  for(i=1; i<argc; i++){
    char *zFile = read_file(argv[i]);
    nErr += scan_file(argv[i], zFile);
    free(zFile);
  }
  return nErr;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<














































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































Deleted tools/schema-info-views.sql.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
-- Source: https://sqlite.org/forum/forumpost/9c83d6f39970e7cc
--
--  Schema Info Views
--
-- This is a set of views providing Schema information
-- for SQLite DBs in table format.
--
DROP VIEW IF EXISTS SysIndexColumns;
DROP VIEW IF EXISTS SysIndexes;
DROP VIEW IF EXISTS SysColumns;
DROP VIEW IF EXISTS SysObjects;
--
CREATE TEMP VIEW SysObjects AS
SELECT ObjectType COLLATE NOCASE, ObjectName COLLATE NOCASE
   FROM (SELECT type AS ObjectType, name AS ObjectName
           FROM sqlite_master
          WHERE type IN ('table', 'view', 'index')
        )
;
--
CREATE TEMP VIEW SysColumns AS
SELECT ObjectType COLLATE NOCASE, ObjectName COLLATE NOCASE, ColumnID 
COLLATE NOCASE,
        ColumnName COLLATE NOCASE, Type COLLATE NOCASE, Affinity COLLATE NOCASE,
        IsNotNull, DefaultValue, IsPrimaryKey
   FROM (SELECT ObjectType, ObjectName, cid AS ColumnID,
   	name AS ColumnName, type AS Type,
                CASE
                  WHEN trim(type) = '' THEN 'Blob'
                  WHEN instr(UPPER(type),'INT' )>0 THEN 'Integer'
                  WHEN instr(UPPER(type),'CLOB')>0 THEN 'Text'
                  WHEN instr(UPPER(type),'CHAR')>0 THEN 'Text'
                  WHEN instr(UPPER(type),'TEXT')>0 THEN 'Text'
                  WHEN instr(UPPER(type),'BLOB')>0 THEN 'Blob'
                  WHEN instr(UPPER(type),'REAL')>0 THEN 'Real'
                  WHEN instr(UPPER(type),'FLOA')>0 THEN 'Real'
                  WHEN instr(UPPER(type),'DOUB')>0 THEN 'Real'
                  ELSE 'Numeric'
                END AS Affinity,
                "notnull" AS IsNotNull, dflt_value as DefaultValue,
		pk AS IsPrimaryKey
           FROM SysObjects
           JOIN pragma_table_info(ObjectName)
        )
;
--
CREATE TEMP VIEW SysIndexes AS
SELECT ObjectType COLLATE NOCASE, ObjectName COLLATE NOCASE,
       IndexName COLLATE NOCASE, IndexID, IsUnique COLLATE NOCASE,
       IndexOrigin COLLATE NOCASE, isPartialIndex
   FROM (SELECT ObjectType,ObjectName,name AS IndexName, seq AS IndexID,
                "unique" AS isUnique, origin AS IndexOrigin,
		partial AS isPartialIndex
           FROM SysObjects
           JOIN pragma_index_list(ObjectName)
        )
;
--
CREATE TEMP VIEW SysIndexColumns AS
SELECT ObjectType COLLATE NOCASE, ObjectName COLLATE NOCASE,
       IndexName COLLATE NOCASE,
       IndexColumnSequence, ColumnID, ColumnName COLLATE NOCASE,
       isDescendingOrder, Collation, isPartOfKey
   FROM (SELECT ObjectType, ObjectName, IndexName, seqno AS 
IndexColumnSequence, cid AS ColumnID,
                name AS ColumnName, "desc" AS isDescendingOrder,
		coll AS Collation, key AS isPartOfKey
           FROM SysIndexes
           JOIN pragma_index_xinfo(IndexName)
        )
;
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<














































































































































Deleted tools/text2c.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>
/**
   A quick hack to dump JavaScript input from stdin
   to a C character array.
*/
int main(int argc, char const ** argv )
{
    int ch = 0;
    int col = 0;
    char const * name;
    if( 2 != argc ) {
        fprintf(stderr, "Usage: %s dataName\n", argv[0]);
        return 1;
    }
    name = argv[1];
    puts("/* auto-generated code - edit at your own risk! (Good luck with that!) */");
    printf("static char const %s_a[] = {\n", name);
    while(EOF != (ch = getchar())) {
        printf("%d, ",ch);
        if( 0 == (++col%20) ) {
            putchar('\n');
            col = 0;
        }
    }
    puts("\n0};");
    /* Not sure why, but without this level of indirection
       i am getting segfaults when dereferencing any byte of the
       array from code which imports it via an 'extern' decl.
    */
    printf("char const * %s = %s_a;\n", name, name);
    return 0;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<