Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From 96e142ed8e To 6eaea2465f
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 | #!/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 | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > | 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@ |
︙ | ︙ |
Changes to auto.def.
1 2 3 4 5 6 7 8 9 10 11 12 13 | # 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." | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > | 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 { |
︙ | ︙ | |||
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | 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: | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | 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: |
︙ | ︙ | |||
91 92 93 94 95 96 97 | 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" } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | 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 } |
Added 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 |
Added 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 |
Added 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 |
Added 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 |
Added 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 |
Added 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 |
Added 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 |
Added 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 ######################################################################## |
Added 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 |
Added 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; } |
Added 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 |
Added 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 |
Added 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 */ |
Added 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 |
Added 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(){}); |
Added 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 |
Added 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"); } |
Added 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 */; |
Added 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, ¤t->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 (¤t); getWindowSize (¤t); disableRawMode (¤t); return current.cols; } char *linenoise(const char *prompt) { int count; struct current current; char buf[LINENOISE_MAX_LINE]; if (enableRawMode(¤t) == -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(¤t); disableRawMode(¤t); 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; } |
Added 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 */ |
Added 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 |
Added 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 |
Added 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}" |
Added 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."); } |
Added 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 } |
Added 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; }); |
Added 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) })); });*/ |
Added 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; } }; |
Added 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(); }); |
Added 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; } }; |
Added 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()); |
Added 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; }); |
Added 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; }); |
Added 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; |
Added 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; }); |
Added 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; }); |
Added 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; |
Added 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; }); |
Added 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; }); |
Added 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; }); |
Added 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); } }; }); |
Added 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; }); |
Added bindings/s2/require.d/fsl/wiki/pageNames.s2.
> > > > | 1 2 3 4 | return requireS2( ['fsl/wiki/util'], proc(util){ return util.getPageNames(); } ); |
Added 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) } }); |
Added 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; |
Added 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; |
Added 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()*/ }; |
Added 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; }); |
Added 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; |
Added 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} }; |
Added 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; } }; |
Added 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; |
Added 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. */ } }); |
Added 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); } } }); |
Added 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 ); } }; }); |
Added 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; |
Added 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)} }; |
Added 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 {} } }; |
Added 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)) } }; |
Added 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); } }; |
Added 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; } }; |
Added 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; } ); |
Added 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 */; |
Added 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; }); }); |
Added 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; |
Added 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) }; |
Added 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!'); }); |
Added bindings/s2/s2_amalgamation.c.
more than 10,000 changes
Added bindings/s2/s2_amalgamation.h.
more than 10,000 changes
Added 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; } |
Added 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; } |
Added 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 |
Added 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); }); }); |
Added bindings/s2/unit/000-000-0empty.s2.
> | 1 | /* valgrind this to find the base s2 script overhead costs. */ |
Added 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 */; |
Added 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; |
Added 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 */; |
Added 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); |
Added bindings/s2/unit/001-011-nameof.s2.
> > > | 1 2 3 | var x; assert nameof x === 'x'; assert 'x' === nameof x; |
Added 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;; }; |
Added 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); |
Added 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*/}; |
Added 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; } |
Added 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; } |
Added 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] */; } |
Added 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; } |
Added 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]; } } |
Added 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(); } |
Added 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. */ } |
Added 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 */; |
Added 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 */; |
Added 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; } |
Added 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; } |
Added 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'; } |
Added 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); } |
Added 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. |
Added 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); }; |
Added 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. */; |
Added 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; |
Added 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 */; |
Added 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; |
Added 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; |
Added 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 */; } |
Added 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; } |
Added 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(); } |
Added 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]; } |
Added 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; } |
Added 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); |
Added 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; |
Added 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); |
Added 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 */; } |
Added 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. } |
Added 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); } |
Added 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; } ;; |
Added 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; } |
Added 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'; }; |
Added 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. */ ; |
Added bindings/s2/unit/Makefile.
> > | 1 2 | all: $(MAKE) -C .. unit |
Added 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; |
Added 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 */; } |
Added 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'"); } |
Added 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. */; } } |
Added bindings/s2/unit2/Makefile.
> > | 1 2 | all: $(MAKE) -C .. unit |
Added 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 | -std=c89 -I include | < < | 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 |
Added 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 |
Added 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/ |
Added 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 | all: include ../subdir-inc.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 | 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)))) |
Deleted fnc/fnc.1.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to fnc/fnc.c.
︙ | ︙ | |||
11 12 13 14 15 16 17 18 | * 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. */ | < < < < < < < < < < < < < < < < < < | | | | | | | > > > > | > > < < < < < < < < < < < < < < | | | | | | | | < | | 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; |
︙ | ︙ | |||
136 137 138 139 140 141 142 | 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. */ | > > | < < < | | | | | | | < | > > | < < < | 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 }, |
︙ | ︙ | |||
231 232 233 234 235 236 237 | "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. */ | < < < < | | < < < < < | < | < < < < < < | 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 | "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 { FILE_ADDED, |
︙ | ︙ | |||
296 297 298 299 300 301 302 | enum fnc_search_state { SEARCH_WAITING, SEARCH_CONTINUE, SEARCH_COMPLETE, SEARCH_NO_MATCH }; | < < < < < < < < < < < < < < < < | 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | 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; fsl_id_t rid; |
︙ | ︙ | |||
380 381 382 383 384 385 386 | pthread_t thread_id; }; struct fnc_diff_view_state { struct fnc_view *timeline_view; struct fnc_commit_artifact *selected_commit; fsl_buffer buf; | < < | > | > | 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; |
︙ | ︙ | |||
446 447 448 449 450 451 452 | 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 *); | | | 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 *, |
︙ | ︙ | |||
472 473 474 475 476 477 478 | 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 *); | < < < | | | | > | < | | < < < < | | > > | | > > | 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)) { |
︙ | ︙ | |||
579 580 581 582 583 584 585 | endwin(); putchar('\n'); if (rc) { if (rc == FCLI_RC_HELP) rc = 0; else { fsl_error_set(&e, rc, NULL); | | | 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); } |
︙ | ︙ | |||
622 623 624 625 626 627 628 | } 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 | | < < | 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); |
︙ | ︙ | |||
648 649 650 651 652 653 654 655 656 657 658 659 | static int init_curses(void) { int rc = 0; initscr(); cbreak(); noecho(); nonl(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE); curs_set(0); | > < < < < < < < < | 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; |
︙ | ︙ | |||
756 757 758 759 760 761 762 | "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) { | | | 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); |
︙ | ︙ | |||
858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 | 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; | > | > > > > > | 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)) |
︙ | ︙ | |||
902 903 904 905 906 907 908 | if (v->active) break; } if (view == NULL && new_view == NULL) { /* No view is active; try to pick one. */ if (prev) view = prev; | | | > | 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; } |
︙ | ︙ | |||
950 951 952 953 954 955 956 | goto end; } rc = view->show(view); if (rc) goto end; if (view->child) { rc = view->child->show(view->child); | < < < < < < < | 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); |
︙ | ︙ | |||
984 985 986 987 988 989 990 | static int show_timeline_view(struct fnc_view *view) { struct fnc_tl_view_state *s = &view->state.timeline; int rc = 0; | | | 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) |
︙ | ︙ | |||
1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 | 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))) | > > | 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 | 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))) |
︙ | ︙ | |||
1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 | 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"); | > > > | 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 | 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"); |
︙ | ︙ | |||
1358 1359 1360 1361 1362 1363 1364 | 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; } | | | | 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); |
︙ | ︙ | |||
1384 1385 1386 1387 1388 1389 1390 | 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); } | | > | | | | > > > > | | | < | < | | | 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(); |
︙ | ︙ | |||
1523 1524 1525 1526 1527 1528 1529 | */ 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; | | | | 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'; |
︙ | ︙ | |||
1553 1554 1555 1556 1557 1558 1559 | * 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; | | | | < | 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'; |
︙ | ︙ | |||
1591 1592 1593 1594 1595 1596 1597 | ++col_pos; } end: fsl_free(date); fsl_free(user); fsl_free(usr_wcstr); fsl_free(pad); | | | 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) |
︙ | ︙ | |||
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))) | > | 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))) |
︙ | ︙ | |||
2016 2017 2018 2019 2020 2021 2022 | 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; | < < < | 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 | 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) { int ch; |
︙ | ︙ | |||
2060 2061 2062 2063 2064 2065 2066 | 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; | < | 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; |
︙ | ︙ | |||
2099 2100 2101 2102 2103 2104 2105 | if ((rc = tl_input_handler(NULL, view, KEY_UP))) return rc; --cur; } } s->search_commit = NULL; | < | < < | 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, ®match, 0) == 0 || regexec(regex, (char *)commit->uuid, 1, ®match, 0) == 0 || regexec(regex, commit->comment, 1, ®match, 0) == 0) return true; return false; } static int view_close(struct fnc_view *view) |
︙ | ︙ | |||
2183 2184 2185 2186 2187 2188 2189 | 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) | | | | 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"); |
︙ | ︙ | |||
2217 2218 2219 2220 2221 2222 2223 | --commits->ncommits; } } static void fnc_commit_artifact_close(struct fnc_commit_artifact *commit) { | < < | | | < < < | | > | | < < < < < < < < < < < < | | | | | | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | < | < < < < < < < < < < < < < < < < < | > > > < | | 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; } |
︙ | ︙ | |||
2489 2490 2491 2492 2493 2494 2495 | "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; | | | | < < < < | | > > > > | 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 | "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; |
︙ | ︙ | |||
2568 2569 2570 2571 2572 2573 2574 | 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) { | < < < < < < < | | | | < | < < | | | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | | < < < < < < < < < < < < < < | | > > > > | | 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; /* |
︙ | ︙ | |||
2738 2739 2740 2741 2742 2743 2744 | if (rc) goto end; rc = fsl_deck_F_rewind(&d1); if (rc) goto end; fsl_deck_F_next(&d1, &fc1); | | < < < < < | | < | > > > | > > | | > > > > > > > > > > > | > > > > > > | > > > > > > > | > > > | > > > | | > | > > > > > > > > > > | < < > | > > | > | | < < < < < | | | < < < < < < < < > | < > | < < < | < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | < < < < < < < < | < | | < < | | < < < < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > | > > < | < < < < < | < < < < < | | | | | | | < < < < < | < < < < < | | | | | 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); } } |
︙ | ︙ | |||
2993 2994 2995 2996 2997 2998 2999 | * 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 { | < | | < | | < | < > > > | | | | | > > | 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; } |
︙ | ︙ | |||
3037 3038 3039 3040 3041 3042 3043 | 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; | | < | 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; } |
︙ | ︙ | |||
3100 3101 3102 3103 3104 3105 3106 | 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; | < | < | | | 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); */ |
︙ | ︙ | |||
3162 3163 3164 3165 3166 3167 3168 | break; } fsl_cx_err_set(f, fsl_errno_to_rc(ferror(s->f), FSL_RC_IO), "getline"); goto end; } | < < < < < < < < < | 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 |
︙ | ︙ | |||
3210 3211 3212 3213 3214 3215 3216 | } end: free(wcstr); free(line); return rc; } | < < < < < < < < < | 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 | } 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)) return false; |
︙ | ︙ | |||
3325 3326 3327 3328 3329 3330 3331 | 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, | | < < < | 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; |
︙ | ︙ | |||
3382 3383 3384 3385 3386 3387 3388 | 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; | < < < | | 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); |
︙ | ︙ | |||
3504 3505 3506 3507 3508 3509 3510 | if (view->searching == SEARCH_FORWARD) start_ln = 1; else start_ln = s->nlines; } while (1) { | | | 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; } |
︙ | ︙ | |||
3550 3551 3552 3553 3554 3555 3556 | return 0; } static int close_diff_view(struct fnc_view *view) { struct fnc_diff_view_state *s = &view->state.diff; | < < | 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) |
︙ | ︙ | |||
3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 | rec_sigwinch = 1; } } static void sigpipe_handler(int sig) { struct sigaction sact; int e; | > > < > > > > > > > > > > > > | | < | | | | | | < < < < < < < < < < | < | < < < < < < < < < < < < < < < < < < | | < > > | | | < < < < < < < < < | < < < > | < < | > > > | < < < < < < < < < < < < < < < < < < < < | < < > | > | > > > | < < < < > | < < < | < < < < | < < > > | < < < < < < < < < < < < < < < | | < < < < < < < < < < < < < < < | < | < | < < < < | | | | | | | 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); } |
Added include/fossil-scm/Makefile.
> > > | 1 2 3 | all clean: $(MAKE) -C ../../ $(MAKECMDGOALS) |
Added 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_ */ |
Added 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 */ |
Added 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 */ |
Added 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 */ |
Added 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_ */ |
Added 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 */ |
Added 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 */ |
Added 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 */ |
Added 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 */ |
Added 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 */ |
Added 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 */ |
Added 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 */ |
Added 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 */ |
Added 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 */ |
Added 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 "<", every ">" into ">", every "&" into "&", and encode '"' as " 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 */ |
Added 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 */ |
Added 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 */ |
Added 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 */ |
Added 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 |
Added resources/fossil-logo-3.png.
cannot compute difference between binary files
Added 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; |
Added 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; |
Added 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); |
Added 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 ; |
Added 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 ; |
Added 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; |
Added 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 ; |
Added 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; |
Added 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; |
Added 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; |
Added 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); |
Added 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 ======================================================================== |
Added 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'); |
Added 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 | all: include ../subdir-inc.make #$(error $(TOP_SRCDIR)) | > > > > | > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < < > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 \ |
︙ | ︙ | |||
43 44 45 46 47 48 49 | sqlite3.o: CFLAGS+=-Wno-sign-compare ifneq (,$(filter clang,$(CC))) # Workaround for clang sqlite3.o: CFLAGS+=-Wno-unused-const-variable endif | > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 ######################################################################## |
Added 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, "<", 4 ); break; case '&': ret += pf( pfArg, "&", 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; } |
Added 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 |
Added 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 |
Added 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 |
Added 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 |
Added 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 |
Added 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 |
Added 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 |
Added 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 |
Added 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 |
Added 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 = ¶mDefaults; } 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 |
Added 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 |
Added 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 |
Added 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 |
Added 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, "<", 4); }else if( c=='&' && p->escHtml ){ rc = fsl_buffer_append(pCol, "&", 5); }else if( c=='>' && p->escHtml ){ rc = fsl_buffer_append(pCol, ">", 4); }else if( c=='"' && p->escHtml ){ rc = fsl_buffer_append(pCol, """, 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, " <", "<"); 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, " > ", ">"); 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 |
Added 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 = "<"; return 4; case '>': *xlate = ">"; return 4; case '&': *xlate = "&"; return 5; case '"': *xlate = """; 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; } } |
Added 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 |
Added 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; } |
Added 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 |
Added 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*/ |
Added 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 |
Added 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(), ¬_used, ¬_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 |
Added 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); } |
Added 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 |
Added 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 |
Deleted src/libfossil-config.h.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted src/libfossil.c.
more than 10,000 changes
Deleted src/libfossil.h.
more than 10,000 changes
Added 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); } |
Added 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 |
Added 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 |
Added 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 |
Added 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; } |
Added 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 |
Added 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; } |
Added 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 */ |
Added 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 */ |
Added 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 */ |
Added 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 */ |
Added 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 */ |
Added 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 */ |
Added 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 */ |
Added 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 |
Added 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; } } |
Added 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 |
Deleted src/sqlite3ext.h.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added 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 ); } |
Added 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 |
Added 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; } |
Added 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; } |
Added 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 |
Added 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 |
Added 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 |
Added 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 |
Added 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 |
Added 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; } |
Added 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) ) ; |
Added 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; } |